Skip to content

Windows (zh-CN): ocx sync always refuses — localized schtasks output breaks the task-absence probe, and the 2s full-listing fallback times out #2914

Description

@ncepuee

Client or integration

Codex CLI (ocx sync on a desktop install; the same refusal surfaces in the dashboard as the failed sync message)

Area

Platform (Windows / macOS / Linux)

Summary

On a Simplified Chinese Windows host (console code page 936/GBK), with the proxy running manually — no opencodex-proxy scheduled task, no WinSW service — ocx sync deterministically refuses to write the Codex config:

Refusing to write because ownership could not be proven: Task Scheduler could not be asked whether opencodex-proxy is registered.

"Absent" is the expected ownership answer in this state (no service, no task), but the probe cannot prove it. Two gaps compound:

  1. The task-not-found check is English-only. SCHTASKS_TASK_NOT_FOUND_EN (src/service-manager-probe.ts) matches only /cannot find the file specified/i. On zh-CN, schtasks /query /tn opencodex-proxy /xml exits 1 with the GBK bytes of 错误: 系统找不到指定的文件。 — and decodeWindowsTextBytes cannot decode those bytes either, because legacyEncodingForLocale (src/lib/windows-text.ts) only maps ko → euc-kr and the windows-1252 family. Chinese (and Japanese) bytes fall through to the UTF-8 fallback and arrive as mojibake, so the regex can never match.
  2. The locale-neutral fallback cannot finish in its budget. The bounded full listing schtasks /query /fo CSV /nh is capped by SERVICE_PROBE_TIMEOUT_MS = 2000. Measured on this host: 401 scheduled tasks, 12.3 s for the full listing — spawnSync returns ETIMEDOUT inside the probe, registered: "unknown", and the write is refused.

A second, related budget surfaces right after the first is worked around: WINDOWS_POWERSHELL_LOOKUP_TIMEOUT_MS = 8000 (src/codex/user-identity.ts). The lookup spawns powershell.exe -NoLogo -NoProfile -NonInteractive -Command including an Add-Type P/Invoke expression. Measured on this host: SID expression 3.2 s, LocalAppData (Add-Type) expression 4.6 s per spawn — with ordinary spawn jitter this intermittently exceeds 8 s and sync fails with CodexUserIdentityRefusal: Windows effective-account lookup timed out. The file's own CI constant already uses 30 000 ms for exactly this reason ("runner contention"), so the desktop ceiling is demonstrably marginal on contended real machines too.

Expected: ownership on a task-less, service-less Windows host should be provable regardless of the OS UI language.

Reproduction

  1. Windows with UI language zh-CN (ANSI code page 936).
  2. Install @bitkyc08/opencodex; run the proxy manually (ocx start); do not install the scheduled task (ocx service install).
  3. Have enough scheduled tasks that schtasks /query /fo CSV /nh takes longer than 2 s (401 on this host), or simply observe the stderr encoding in step 4.
  4. Run ocx sync → refused with the ownership message above. Under bun, calling inspectServiceManagerInstallation() reproduces it deterministically:
{ "kind": "unknown", "reason": "Task Scheduler could not be asked whether opencodex-proxy is registered" }

Evidence measured on the affected host (Windows 10 22H2, zh-CN, opencodex 2.35.0):

  • schtasks /query /tn opencodex-proxy /xml → exit 1, stderr = 31 GBK bytes, correctly decodable via new TextDecoder("gbk") (错误: 系统找不到指定的文件。), but decodeWindowsTextBytes returns mojibake (its legacy table lacks zh).
  • schtasks /query /fo CSV /nh → 401 tasks, 12.3 s wall time; inside the probe it is killed at 2 s (ETIMEDOUT).
  • identity lookups under bun: SID 3.15 s, LocalAppData 4.6 s; a bare powershell.exe -NoProfile -Command exit costs ~3 s on this machine vs ~150 ms on a typical desktop.

Version

2.35.0 (installed via npm install -g, bun runtime)

Operating system

Windows 10 22H2 (build 19045), UI language zh-CN

Provider and model

Not provider-specific.

Logs or error output

$ ocx sync
Codex sync did not complete. Fix the reported Codex config issue and retry.

# underlying admission refusal (surfaced by the dashboard as 同步失败:...):
Refusing to write because ownership could not be proven: Task Scheduler could not be asked whether opencodex-proxy is registered.

# after patching only the probe, the next stage fails on a contended desktop:
Codex configuration was not written: the coordinator path could not be resolved:
CodexUserIdentityRefusal: Windows effective-account lookup timed out..

# probe-side warning seen during sync on this host:
[opencodex] ACL hardening timed out (ETIMEDOUT) — transient icacls stall; ... continuing without NTFS ACL harden

Suggested fix

  • legacyEncodingForLocale: add zh → gbk (big5 for zh-Hant) and ja → shift_jis — all WHATWG labels the runtime already exposes, so no new dependency.
  • Extend SCHTASKS_TASK_NOT_FOUND_EN with the localized messages (e.g. 找不到指定的文件, 指定されたファイルが見つかりません), or make the absence check locale-independent.
  • Consider aligning the desktop identity-lookup budget with the CI one (30 s), or reusing the existing per-process memo so admission stages don't re-pay the spawn cost.

For reference, a local patch of exactly those three points was verified on the affected host: probe unknown → absent, admission refused → admitted, and ocx sync then exits 0 (720 models written to the catalog).

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
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Aug 29, 2026
  2. lidge-jun commented on Aug 29, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 72 / 80

    이 이슈는 중국어 Windows 에서 ocx sync 가 항상 거절되는 실사용 버그다. 지금 dev HEAD 는 a0a8e8e2b (#2912) 이다. Windows 쪽은 #2857 이 스케줄러 홈 범위를 고쳤고, #1573 과 #1933 이 decodeWindowsTextBytes 로 schtasks/레지스트리 바이트를 읽게 했다. 그 디코더는 한국어(ko → euc-kr)와 서유럽 1252만 안다. 중국어 호스트는 그 테이블 밖이다. 작업이 없고 서비스도 없으면 소유 답은 "없음" 이어야 한다. 없으면 이 홈이 설정 파일을 써도 된다. 프로브가 없음을 증명 못 하면 unknown 이 되고, 소유를 못 밝혔다고 sync 가 거절한다.

    첫 구멍은 파일 없음 검사가 영어만 본다는 점이다. src/service-manager-probe.ts 539행 SCHTASKS_TASK_NOT_FOUND_EN 은 cannot find the file specified 만 본다. zh-CN 에서 schtasks /query /tn opencodex-proxy /xml 은 종료 코드 1과 GBK 바이트를 낸다. 글자는 错误: 系统找不到指定的文件。 이다. src/lib/windows-text.ts 57행 legacyEncodingForLocale 은 ko 와 windows-1252 언어만 돌려준다. zh 는 null 이라 UTF-8 폴백이 되고 글자가 깨진다. 깨진 글자에는 영어 문장이 없으니 정규식이 절대 안 맞는다.

    두 번째 구멍은 그 다음 폴백이다. 영어가 아니면 schtasks /query /fo CSV /nh 전체 목록을 2초 안에 읽는다. SERVICE_PROBE_TIMEOUT_MS 는 33행에 2000이다. 주석도 "느린 답은 없는 답과 같다"고 적혀 있다. 제보 호스트는 작업 401개에 12.3초가 걸린다. 2초에 끊기면 timedOut 이고 registered: unknown 이다. 쓰기가 거절된다.

    그 다음 단계가 또 있다. src/codex/user-identity.ts 40행 데스크톱 PowerShell 조회 한도는 8초다. CI만 54행에서 30초다. 주석은 8초가 진짜 데스크톱에 넉넉하다고 적는다. 제보 호스트는 powershell 빈 실행만 약 3초, SID 3.2초, LocalAppData 4.6초다. 첫 프로브를 고쳐도 여기서 가끔 또 거절된다.

    types.ts/config.ts 분할과 무관하다. 독립 버그픽스다. close-don't-rebase 대상이 아니다. 제보자가 로컬에서 디코드/문장/한도 셋을 고치면 프로브가 unknown → absent 가 되고, 입장이 refused → admitted 가 되고, ocx sync 가 0으로 끝났다.

    경로 src/lib/windows-text.ts 57행 - zh 와 ja 가 없다. 같은 파일 51-55행은 CP932 바이트를 1252로 읽으면 경로가 조작된다고 경고한다. ja 를 넣을 때는 shift_jis 를 그 로케일에만 묶어야 한다. zh-Hant 는 gbk 가 아니라 big5 쪽이 맞을 수 있다.

    라인 src/service-manager-probe.ts 539 - 영어 한 줄만 absent 확정이다. 디코드가 되면 중국어/일본어 문장을 넣거나, 디코드 성공 뒤에 로케일 중립 신호를 써야 한다. 종료 코드 1만으로 없음이라고 단정하면 다른 실패와 섞인다.

    라인 src/service-manager-probe.ts 33, 577 - 2초 전체 목록은 작업이 많은 호스트에서 항상 진다. 한도를 올리는 건 입장(admission)을 늦춘다. 이름 조회가 로케일을 이기면 이 목록은 안 타도 된다.

    라인 src/codex/user-identity.ts 40 vs 54 - 8초는 "진짜 데스크톱은 빠르다"는 가정이다. 제보 기계는 그 가정이 아니다. 한도를 올리면 멈춘 자식이 더 오래 버틴다. 프로세스 안 메모가 있으면 입장 단계가 스폰 비용을 반복해 안 내도 된다.

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

    • 1차 고침을 디코드 + 파일없음 문장으로 할지, schtasks 종료 코드만으로 할지
    • zh-Hans 는 gbk 인지 gb18030 인지, zh-Hant 는 big5 인지
    • ja → shift_jis 를 파일의 CP932 경고와 어떻게 맞출지
    • identity 8초를 같이 올릴지, 메모만 따로 할지. 제보자는 로컬에서 셋 다 고쳐서 sync 가 통과했다

    너의 추천
    독립 버그픽스로 받아라. 전체 목록 2초를 올리는 건 나중이고, 먼저 legacyEncodingForLocale 에 zh(및 필요하면 ja)를 넣고 파일없음 검사를 디코드된 문장으로 통과시켜라. 그러면 작업이 없는 호스트는 12초 목록을 안 탄다. identity 타임아웃은 별 커밋으로 메모 또는 한도를 검토해라. 제보자 로컬 패치가 이미 그 순서로 동작했다.

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

  3. added a commit that references this issue on Aug 29, 2026
    aaccef7
  4. lidge-jun commented on Aug 29, 2026

    @lidge-jun
    Owner

    Fixed on dev in the merge below (#2918).

    Your diagnosis was right on all three points, and the measurements are what made it actionable — the 401-task/12.3 s figure is the reason the second gap is a real defect rather than a theoretical one.

    What shipped: zh → gbk, zh-Hant/zh-TW/zh-HK/zh-MO → big5, ja → shift_jis, and the full listing gets its own 20 s budget through a per-call override while the targeted queries keep the 2 s admission ceiling. I checked each TextDecoder exists and rejects mismatched bytes fatally before relying on it, and the test fixtures are real generated bytes rather than hand-typed hex — my first hand-written attempt was wrong and the test caught it.

    One suggestion I tried and rejected. Rather than add localized substrings, I attempted the general version: query a task name nothing can own, and treat whatever comes back as that host's not-found signature. It is unsafe. schtasks exits 1 for both "task not found" and "access denied", so on a locked-down host the control and real queries return the same message, the signatures match, and the probe reports absent — a false clean machine, in the one direction that lets a write proceed into a home another process owns. The repository's existing access-denied test went red immediately. The reasoning is now in the source comment so it does not get rebuilt later. That leaves the listing as the only locale-neutral evidence, which is exactly why its budget was the fix.

    Deliberately not included: the WINDOWS_POWERSHELL_LOOKUP_TIMEOUT_MS = 8000 ceiling. Your measurements make a good case that it is marginal on a contended desktop, but it is a different subsystem with a different failure mode, and the better fix is plausibly reusing the per-process memo rather than a larger number. It should not ride along inside a probe fix — please open a separate issue if you would like it tracked, or say so here and I will.

    I could not reproduce your host: I am on macOS with no zh-CN Windows machine, so the byte handling and the timeout arithmetic are verified here, and the end-to-end ocx sync recovery rests on your report.

  5. added 3 commits that reference this issue on Aug 29, 2026
    8f199fc
    1d9b389
    09a5029
  6. added 4 commits that reference this issue on Sep 14, 2026
    42a8251
    d89e670
    83385af
    b90250e
  7. added 5 commits that reference this issue on Sep 17, 2026
    52bd56f
    5d2ed10
    d2f2029
    bd4061f
    4b2adf4
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 flagsplatformOS/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