Skip to content

Windows: ocx service install rolls back with a non-ASCII profile path #3064

Closed

Description

@chowyuan1314

Client or integration

Codex CLI

Area

Service lifecycle

Summary

On Windows, a fresh ocx service install can create the opencodex-proxy Scheduled Task, then reject it as unhealthy and roll it back when the Windows user-profile path contains non-ASCII characters.

Expected: the valid task passes post-create verification and remains registered.

Actual: the install command rolls back the new task.

Reproduction

  1. Use a Windows account whose profile directory name contains non-ASCII characters.
  2. Install OpenCodex globally with npm install -g @bitkyc08/opencodex.
  3. Run ocx service install.
  4. Observe that opencodex-proxy is created and then rolled back.

Diagnostic result: querying the task with schtasks /query /tn opencodex-proxy /xml returns the expected launcher path when its native output bytes are decoded directly. In contrast, the runFile() path used by src/service.ts supplies text that has already undergone code-page conversion, corrupting the non-ASCII part of <Arguments>. The subsequent task-registration health comparison fails.

A narrowly scoped code change that resolves the issue is to use Bun.spawnSync({ cmd: [windowsSchtasks(), ...args] }) only for /xml task queries, then pass Buffer.from(result.stdout) to decodeSchtasksOutput(); retain the existing runFile() fallback for all other queries.

Version

2.37.0

Operating system

Windows 11 24H2 (x64)

Provider and model

Not applicable; this occurs before any provider request.

Logs or error output

ocx service install

Service install cleanup failed: Task Scheduler registration was created but failed the OpenCodex action/trigger or attempt-ownership verification. The invalid registration was rolled back.

The exact local profile path has been intentionally redacted.

Screenshots and supporting files

None.

Redacted configuration

No provider or routing configuration is required to reproduce. This is a default global npm installation followed by ocx service install.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    cliCLI, config inject, packaging flags
    installInstallation or packaging
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Aug 31, 2026
  2. lidge-jun commented on Aug 31, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 68 / 80

    이 이슈는 Windows에서 사용자 프로필 경로에 비ASCII(한글·일본어 등)가 있을 때 ocx service install이 Task Scheduler 작업 opencodex-proxy를 만든 뒤, 사후 검증에서 unhealthy로 보고 롤백한다는 보고입니다. 기대는 방금 만든 유효한 등록이 그대로 남는 것이고, 실제는 "created but failed … verification. The invalid registration was rolled back."로 사라집니다. 보고 버전은 2.37.0, OS는 Windows 11 24H2입니다. 지금 dev HEAD는 f83368dfd(패키지 2.38.0)이고, 관련 코드는 여전히 src/service.ts 한곳에 모여 있습니다.

    HEAD를 보면 이미 UTF-16 schtasks 출력 대응이 들어가 있습니다. decodeSchtasksOutput(대략 865행 근처)는 BOM/짝수 null 패턴으로 UTF-16LE를 고르고, runFile(888행 근처)는 execFileSync(..., { encoding: "buffer" })로 받은 버퍼를 그 디코더에 넘깁니다. querySchtasks(["/query", "/tn", ..., "/xml"]) → verifyWindowsSchedulerInstall(1135행 근처) → windowsTaskRegistrationHealthy(2038행 근처)가 Arguments 안의 런처 경로를 taskXmlDecodedValueEquals로 비교합니다. #2918이 CJK schtasks 디코드와 목록 완주를 고친 이력도 있습니다. 그런데도 이 보고는 "수동으로 받은 원본 바이트를 직접 디코드하면 경로가 맞고, runFile 경로로 온 텍스트는 코드 페이지 변환이 이미 섞여 <Arguments>가 깨진다"고 짚습니다. 즉 HEAD의 buffer+UTF-16 가드가 있어도, Windows에서 execFileSync가 콘솔 코드 페이지로 한 번 지난 바이트를 주면 디코더가 UTF-8/UTF-16 어느 쪽으로도 복구하지 못한다는 가설입니다. 제안 수정은 /xml 쿼리만 Bun.spawnSync({ cmd: [windowsSchtasks(), ...] })로 바꿔 Buffer.from(result.stdout)를 decodeSchtasksOutput에 넘기고, 나머지 쿼리는 기존 runFile을 유지하는 좁은 변경입니다.

    라인 - src/service.ts runFile/querySchtasks: /xml일 때 spawn 경로를 타는지 여부가 이 버그의 핵심. 목록·CSV·create/delete는 기존 경로 유지가 맞음.

    라인 - windowsTaskRegistrationBaseHealthy의 Arguments 비교(/b /nologo "…launcher…"): 런처가 %USERPROFILE% 아래 비ASCII면, XML 디코드가 깨지는 순간 unhealthy → 롤백으로 이어짐. 증상과 일치.

    라인 - verifyWindowsSchedulerInstall의 디스크 XML 폴백(1137행 근처): 쿼리가 빈 문자열일 때만 디스크 문서를 씀. 쿼리가 모지바케 문자열을 성공적으로 반환하면 폴백이 안 타서, 깨진 비교가 그대로 실패함. spawn 수정이 우선이고, 필요하면 "쿼리 성공이지만 Arguments 불일치 + 디스크 XML은 healthy"일 때 디스크를 신뢰할지 별도 판단.

    라인 - 테스트: Linux CI만으로는 재현 불가. decodeSchtasksOutput에 코드 페이지로 이미 깨진 바이트 픽스처를 넣어도 spawn vs execFileSync 차이를 증명하진 못함. Windows 실기기 또는 모의 queryXml deps로 회귀를 잠글 것.

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

    • HEAD(2.38.0)에서 같은 비ASCII 프로필로 재현되는지. 2.37.0 전용인지, spawn 전환이 아직 필요한지.
    • Bun.spawnSync만 /xml에 쓸지, runFile 전체를 Bun으로 통일할지. 후자는 면적이 커짐.
    • 모지바케 쿼리 성공 시 디스크 XML 폴백을 허용할지. 허용하면 위조·레이스 면밀히 볼 것.

    너의 추천
    도움 환영 이슈로 두고, 재현 확인 후 /xml 전용 Bun.spawnSync → decodeSchtasksOutput 패치를 받는 쪽이 맞다. PR은 src/service.ts와 Windows 단위/시나리오 테스트만. types/config 스플릿과 무관. 이미 #2918 계열이 있으면 "continues #2918"로 적고, 디스크 폴백 확장은 기본 패치에 넣지 말고 재현 로그 보고 결정하라.

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

  3. added 3 commits that reference this issue on Aug 31, 2026
    e5f9054
    ff3cee3
    912912a
  4. lidge-jun commented on Sep 1, 2026

    @lidge-jun
    Owner

    Fixed on dev by b14b741dc (#3134, rebase carry of #3104).

    schtasks /query /xml converts its output through the console code page before the bytes exist, so reading the stream as a buffer cannot recover them — a profile named outside that page comes back as C:\Users\???\..., and the exact string comparison then rejected a registration this very process had just created correctly. That rejection is what rolled the install back.

    The comparison now tolerates the mangling, but narrowly: each unrepresentable run in the expected value may match only a run of substitution characters — never arbitrary text, and never a path separator (src/service.ts:2011-2013).

    The narrowness is the point. A wildcard as wide as [^\\/]* forbids a separator but allows arbitrary ASCII, so a wholly non-ASCII segment would lose every anchor and C:\Users\<CJK>\.opencodex\service-launcher.vbs would match C:\Users\Admin\.opencodex\service-launcher.vbs — this process could then adopt, repair, or delete another account's scheduled task. <UserId> had the same hole and is now handled by exact SID matching instead.

    Closing manually: PRs here target dev, so GitHub's auto-close on Closes # does not fire.

  5. github-actions commented on Sep 1, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

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 workingcliCLI, config inject, packaging flagsinstallInstallation or packagingplatformOS/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