Skip to content

[Bug]: Elevated Task Scheduler re-registration exceeds command-line limit — ENAMETOOLONG (uv_spawn) with double base64 XML payloads #4692

Closed

Description

@mustardwheat

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:

Environment

  • OS: Windows 11, zh-CN
  • opencodex 2.54.0 (same code in 2.55.0)

Reproduction

  1. Register opencodex-proxy from an elevated installer (task file owned by Administrators).
  2. 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 identityUpgradeNeeded stays true.
  3. ocx service repair → non-elevated schtasks /create /f returns Access 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

  1. added
    bugSomething isn't working
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Sep 15, 2026
  2. lidge-jun commented on Sep 15, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 71 / 80

    이 이슈는 elevated Task Scheduler 재등록이 명령줄 길이 한도를 넘겨 ENAMETOOLONG: name too long, uv_spawn으로 죽는 버그다. 스택은 리포터와 현재 dev가 같다. HEAD 270291170(패키지 2.56.0)의 src/lib/windows-elevation.ts runWindowsElevatedScheduledTaskRegistration(약 652–704행) → startPowerShellCommand(약 523행대) → windows-ops.ts registerFreshWindowsSchedulerTask 약 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 한도를 넘기 쉽다. 관리자 소유 작업이면 plain schtasks /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.ts 652–704 (runWindowsElevatedScheduledTaskRegistration) - replace 시 XML 두 개를 double-base64로 EncodedCommand에 넣어 spawn 한도를 넘긴다.
    라인 src/lib/windows-elevation.ts startPowerShellCommand - 긴 -Command 스크립트를 그대로 spawn; ENAMETOOLONG이 여기서 WindowsElevationError로 감싸진다.
    라인 src/service/windows-ops.ts 276–313 - access-denied 때 elevated fallback으로 위 함수를 호출. admin-owned task면 항상 이 분기.
    라인 src/service/repair.ts 171–212 - identity/priority upgrade 때 reregister; #4691과 겹치면 매 수리마다 elevated 경로.
    경로 hardenSecretPath (windows-secret-acl.ts) - staging 파일 ACL 하드닝에 재사용 가능한 기존 도구.

    메인테이너의 판단이 필요한 지점

    너의 추천
    #4691 한 줄 위임을 먼저 머지하고, 이어서 elevated 등록을 ACL-hardened staging(또는 동등한 temp script)으로 바꾸는 PR을 연다. 회귀 테스트는 ‘큰 XML + replace precondition’ fixture로 spawn argv 길이가 한도 아래인지, 또는 staging 경로 모드가 쓰이는지를 고정한다. 문서/릴리스 노트에 워라운드(관리자 작업 삭제 후 재등록)를 짧게 남겨도 좋다. 이슈는 bug 유지, #4691과 크로스링크. 2.56.0 promote와 묶지 말 것.

    이 댓글은 grok-bot이 작성했습니다

  3. lidge-jun commented on Sep 16, 2026

    @lidge-jun
    Owner

    Landed via #4759 at ac87703

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)serviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions