Summary
On a zh-CN Windows host (OEMCP/ACP 936), ocx service repair — and the Dashboard repair/install buttons — fail against a registration that OpenCodex itself created:
Service repair failed: Task Scheduler registration is not a recognized legacy OpenCodex definition; it was preserved for manual review.
Root cause: redirected schtasks /query /xml output follows the console output code page of the spawning process tree, not the XML document encoding. In any 936 console context (including the no-console background service on a zh-CN host), the bytes are GBK. decodeSchtasksOutput (src/service/windows-scheduler.ts, moved from src/service.ts in the 2.54 refactor) probes UTF-16 and then falls back to a plain UTF-8 decode, which turns the CJK account name inside <SessionStateChangeTrigger><UserId> into U+FFFD replacement characters. The correctly resolved expected identity [SID, MACHINE\user] then never matches the trigger scope, windowsTaskRegistrationHealthy() returns false, and repair aborts at the recognition gate. The same mojibake also rolls back fresh installs at the post-create verification ("...registration was created but failed the OpenCodex action/trigger or attempt-ownership verification. The invalid registration was rolled back.").
This is the remaining unwired call site of the decoder the project already built for exactly this class: decodeWindowsTextBytes (src/lib/windows-text.ts, UTF-16 → strict UTF-8 → locale legacy page incl. GBK) already fixed the sibling whoami/PowerShell decode in src/lib/windows-user-principal.ts (#2914, and #722 for CP949). Related: #3064 (non-ASCII profile path), #4106 (closed as not-planned; that reporter's console was CP 65001 — a different manifestation. The failure below reproduces deterministically under any 936 console).
Environment
- OS: Windows 11, system locale zh-CN (ACP=936, OEMCP=936)
- Local account name contains CJK characters
- opencodex: observed on 2.47.0 → 2.55.0; dev branch fetched 2026-09-08 still has the same fallback
Reproduction
ocx service install on a zh-CN host with a CJK account name. The task registers with SID-scoped session triggers; Task Scheduler export canonicalizes the SID back to the CJK account name.
- From a console with
chcp 936 (or via the Dashboard, whose repair child runs without a console under the 936 service context), run ocx service repair.
- Observe the recognition error above (exit code 1; the subsequent serving check still prints the "repaired and serving" line because the proxy was never stopped).
Instrumented A/B against the real repairService (2.54.0, chcp 936):
statusWindowsXml() returns trigger scopes as MACHINE\???? (U+FFFD); resolveWindowsTaskDiagnosticUserId() returns the correct [SID, MACHINE\<CJK-name>]; windowsTaskRegistrationHealthy() → false → gate throws.
- Same bytes through
decodeWindowsTextBytes → correct account name; healthy → true; ocx service repair exits 0.
Suggested fix
One-line delegation plus import (running locally as a hotfix since 2.47.0; every npm i -g overwrite brings the bug back):
import { decodeWindowsTextBytes } from "../lib/windows-text";
export function decodeSchtasksOutput(buffer: Buffer): string {
return decodeWindowsTextBytes(buffer);
}
Logs
❌ Service repair failed: Task Scheduler registration is not a recognized legacy OpenCodex definition; it was preserved for manual review.
✅ opencodex service repaired and serving on port 10100. (serving check still passes; exit code is 1)
Summary
On a zh-CN Windows host (OEMCP/ACP 936),
ocx service repair— and the Dashboard repair/install buttons — fail against a registration that OpenCodex itself created:Root cause: redirected
schtasks /query /xmloutput follows the console output code page of the spawning process tree, not the XML document encoding. In any 936 console context (including the no-console background service on a zh-CN host), the bytes are GBK.decodeSchtasksOutput(src/service/windows-scheduler.ts, moved fromsrc/service.tsin the 2.54 refactor) probes UTF-16 and then falls back to a plain UTF-8 decode, which turns the CJK account name inside<SessionStateChangeTrigger><UserId>into U+FFFD replacement characters. The correctly resolved expected identity[SID, MACHINE\user]then never matches the trigger scope,windowsTaskRegistrationHealthy()returns false, and repair aborts at the recognition gate. The same mojibake also rolls back fresh installs at the post-create verification ("...registration was created but failed the OpenCodex action/trigger or attempt-ownership verification. The invalid registration was rolled back.").This is the remaining unwired call site of the decoder the project already built for exactly this class:
decodeWindowsTextBytes(src/lib/windows-text.ts, UTF-16 → strict UTF-8 → locale legacy page incl. GBK) already fixed the siblingwhoami/PowerShell decode insrc/lib/windows-user-principal.ts(#2914, and #722 for CP949). Related: #3064 (non-ASCII profile path), #4106 (closed as not-planned; that reporter's console was CP 65001 — a different manifestation. The failure below reproduces deterministically under any 936 console).Environment
Reproduction
ocx service installon a zh-CN host with a CJK account name. The task registers with SID-scoped session triggers; Task Scheduler export canonicalizes the SID back to the CJK account name.chcp 936(or via the Dashboard, whose repair child runs without a console under the 936 service context), runocx service repair.Instrumented A/B against the real
repairService(2.54.0, chcp 936):statusWindowsXml()returns trigger scopes asMACHINE\????(U+FFFD);resolveWindowsTaskDiagnosticUserId()returns the correct[SID, MACHINE\<CJK-name>];windowsTaskRegistrationHealthy()→ false → gate throws.decodeWindowsTextBytes→ correct account name; healthy → true;ocx service repairexits 0.Suggested fix
One-line delegation plus import (running locally as a hotfix since 2.47.0; every
npm i -goverwrite brings the bug back):Logs
ocx service installrolls back with a non-ASCII profile path #3064, Windows service management breaks on non-ASCII (Chinese) usernames / UTF-8 codepage: 'ownership could not be proven' and 'registered as .\���� ... rolled back' #4106 are related but cover other call sites/manifestations).