Repository navigation
[Bug]: Elevated Task Scheduler re-registration exceeds command-line limit — ENAMETOOLONG (uv_spawn) with double base64 XML payloads #4692
Description
Summary
When ocx service repair re-registers the Task Scheduler task through the elevated fallback, the spawn fails with:
WindowsElevationError: ENAMETOOLONG: name too long, uv_spawn
at startPowerShellCommand (…/src/lib/windows-elevation.ts:560)
at runWindowsElevatedScheduledTaskRegistration (…/src/lib/windows-elevation.ts:704)
at registerFreshWindowsSchedulerTask (…/src/service/windows-ops.ts:313)
at reregisterWindowsSchedulerTask (…/src/service/windows-ops.ts:430)
at repairService (…/src/service/repair.ts:212)
runWindowsElevatedScheduledTaskRegistration embeds two full XML payloads — the new task XML and the expected-existing snapshot (replace precondition) — as base64(utf16le) inside an inner PowerShell script, which is then itself base64(utf16le)-encoded into the outer -EncodedCommand. With a ~2 KB task XML this pushes the outer command line past the Windows spawn limit. Observed on 2.54.0; the snapshot precondition added the second payload, and the same code is present in 2.55.0.
Two aggravating factors on affected hosts:
- On hosts where Task Scheduler export canonicalizes trigger SIDs back to account names (e.g. a CJK account name, see [Bug]: decodeSchtasksOutput UTF-8 fallback mangles GBK schtasks output on zh-CN Windows — service repair/install fails with CJK account name #4691),
identityUpgradeNeededis true on every repair, so the re-register path runs every time, not once. - If the task was originally registered by an elevated process (admin-owned task file), plain
schtasks /create /fis denied, so every repair funnels into this elevated path — repair can never exit 0.
Environment
- OS: Windows 11, zh-CN
- opencodex 2.54.0 (same code in 2.55.0)
Reproduction
- Register
opencodex-proxyfrom an elevated installer (task file owned by Administrators). - Use an account whose session-trigger scope exports as an account name rather than the SID (a CJK account name works, per [Bug]: decodeSchtasksOutput UTF-8 fallback mangles GBK schtasks output on zh-CN Windows — service repair/install fails with CJK account name #4691) so
identityUpgradeNeededstays true. ocx service repair→ non-elevatedschtasks /create /freturnsAccess is denied→ elevated fallback →ENAMETOOLONG: name too long, uv_spawn.
Suggested fix
Pass the XML through an ACL-protected staging file (readable only by the elevated process) and reference its path in the elevated command — or write the inner script to a temp .ps1 — instead of double-base64 inline payloads. The current "immutable bytes, never a caller-writable pathname" design goal can be preserved by ACL-hardening the staged file (similar to hardenSecretPath).
Workaround that unblocked us: delete the admin-owned task once from an elevated prompt (schtasks /delete /tn opencodex-proxy /f), then let repair re-create it as the current user; afterwards the non-elevated replace succeeds and the elevated path is never needed.
- I searched existing issues and documentation (no prior ENAMETOOLONG report found).
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Sep 15, 2026
리뷰 · 우선순위 71 / 80
이 이슈는 elevated Task Scheduler 재등록이 명령줄 길이 한도를 넘겨
ENAMETOOLONG: name too long, uv_spawn으로 죽는 버그다. 스택은 리포터와 현재dev가 같다. HEAD270291170(패키지 2.56.0)의src/lib/windows-elevation.tsrunWindowsElevatedScheduledTaskRegistration(약 652–704행) →startPowerShellCommand(약 523행대) →windows-ops.tsregisterFreshWindowsSchedulerTask약 307행 →reregisterWindowsSchedulerTask→repairService약 212행.설계 의도는 분명하다. 주석도 ‘immutable bytes, never a caller-writable pathname’이라고 적혀 있다. replace일 때 새 task XML과 expected-existing 스냅샷을 둘 다 utf16le base64로 inner PowerShell에 넣고, 그 inner 전체를 다시 utf16le base64로 outer
-EncodedCommand에 넣는다(661–686행). XML이 ~2KB만 되어도 outer 커맨드가 Windows spawn 한도를 넘기 쉽다. 관리자 소유 작업이면 plainschtasks /create /f가 Access denied → 매번 elevated fallback으로 떨어진다. #4691로 UserId가 깨지면identityUpgradeNeeded가 매 수리마다 참이라 이 경로가 ‘한 번’이 아니라 ‘항상’이 된다. 그래서 repair가 exit 0을 못 낸다.워라운드(elevated에서 작업 삭제 후 현재 사용자로 재생성)는 설명과 코드 흐름에 맞다. 비 elevated replace가 성공하면 double-base64 경로를 안 탄다. 근본 수정은 리포터 제안대로 ACL로 잠근 staging 파일(또는 temp
.ps1)에 XML을 두고 elevated 커맨드에는 경로만 넘기는 쪽이 자연스럽다. 이미hardenSecretPath(src/lib/windows-secret-acl.ts)가 있으니 ‘호출자가 마음대로 쓰는 pathname’ 위험을 같은 패턴으로 줄일 수 있다. 단, UAC 전·후에 predecessor 스냅샷 비교(precondition) 계약은 유지해야 한다.라인
src/lib/windows-elevation.ts652–704 (runWindowsElevatedScheduledTaskRegistration) - replace 시 XML 두 개를 double-base64로 EncodedCommand에 넣어 spawn 한도를 넘긴다.라인
src/lib/windows-elevation.tsstartPowerShellCommand- 긴-Command스크립트를 그대로 spawn; ENAMETOOLONG이 여기서 WindowsElevationError로 감싸진다.라인
src/service/windows-ops.ts276–313 - access-denied 때 elevated fallback으로 위 함수를 호출. admin-owned task면 항상 이 분기.라인
src/service/repair.ts171–212 - identity/priority upgrade 때 reregister; #4691과 겹치면 매 수리마다 elevated 경로.경로
hardenSecretPath(windows-secret-acl.ts) - staging 파일 ACL 하드닝에 재사용 가능한 기존 도구.메인테이너의 판단이 필요한 지점
.ps1중 무엇을 표준으로 할지.너의 추천
#4691 한 줄 위임을 먼저 머지하고, 이어서 elevated 등록을 ACL-hardened staging(또는 동등한 temp script)으로 바꾸는 PR을 연다. 회귀 테스트는 ‘큰 XML + replace precondition’ fixture로 spawn argv 길이가 한도 아래인지, 또는 staging 경로 모드가 쓰이는지를 고정한다. 문서/릴리스 노트에 워라운드(관리자 작업 삭제 후 재등록)를 짧게 남겨도 좋다. 이슈는
bug유지, #4691과 크로스링크. 2.56.0 promote와 묶지 말 것.이 댓글은 grok-bot이 작성했습니다
fix(service): report an unreadable staged payload with its cause (#4692)
test(service): move elevated-staging cover out of the ratcheted servi…
Merge pull request #4759 from lidge-jun/codex/win3-elevated-registrat…
lidge-jun commentedon Sep 16, 2026
Landed via #4759 at ac87703
fix(service): stage elevated Task Scheduler XML instead of inlining it (
fix(service): stage elevated Task Scheduler XML instead of inlining it (
fix(service): report an unreadable staged payload with its cause (lid…
test(service): move elevated-staging cover out of the ratcheted servi…
Merge pull request lidge-jun#4759 from lidge-jun/codex/win3-elevated-…