Repository navigation
Description
Summary
On non-English Windows (and generally under the bundled Bun runtime), ocx service aborts during install with a false "native service still exists" error, even when the opencodex-proxy-native SCM service does not exist. The same faulty detection path is reused by ocx update (its service-restart step), so updating a service-managed proxy is affected too.
Root cause: probeScmRegistration() in src/lib/winsw.ts cannot recognize sc.exe's ERROR_SERVICE_DOES_NOT_EXIST (1060) result, so statusWinswRaw() returns "unknown" instead of "nonexistent", and installWindows() refuses to proceed.
Environment
- OS: Windows 11 (pt-BR locale), 10.0.26200
- opencodex: 2.7.28 (reproduces the same code on 2.7.30)
- Runtime: bundled Bun 1.3.14
- Install: global via fnm-managed Node
Symptom
$ ocx service
error: Cannot remove the native service before switching to Task Scheduler:
Cannot verify the native service registration (sc.exe query failed).
Uninstall aborted; check 'sc query opencodex-proxy-native' and retry.
at installWindows (.../src/service.ts:483)
…yet the service genuinely does not exist:
$ sc query opencodex-proxy-native
[SC] EnumQueryServicesStatus:OpenService FALHA 1060:
O serviço especificado não existe como serviço instalado.
There is no ~/.opencodex/winsw/ directory and no opencodex-proxy-native SCM registration.
Root cause
statusWinswRaw() → probeScmRegistration() (src/lib/winsw.ts) classifies the sc query failure. Only a confirmed 1060 is allowed to prove absence; anything else becomes "error" → statusWinswRaw() returns "unknown" → lifecycle callers assume a service may exist. The 1060 check has two portability gaps:
- Exit-code truncation under Bun.
sc.exeexits with code1060, but Bun on Windows truncates the process exit status to 8 bits, so the thrown error reportse.status === 36(1060 % 256 = 36). The guarde.status === 1060is therefore never true under Bun. - English-only text match. The fallback
/FAILED 1060/idoes not match localizedsc.exeoutput. On pt-BR the line is... FALHA 1060: ..., so the regex misses it.
With both signals failing, probeScmRegistration() returns "error" (fail-closed), which is the intended behavior for a genuinely unreadable SCM — but here the SCM answered clearly, we just failed to parse a localized/truncated "does not exist".
Reproduction (Bun on Windows)
const { execFileSync } = require("node:child_process");
const { join } = require("node:path");
const sc = join(process.env.SystemRoot || "C\\Windows", "System32", "sc.exe");
try {
execFileSync(sc, ["query", "opencodex-proxy-native"], { encoding: "utf8", stdio: ["ignore","pipe","pipe"] });
} catch (e) {
const text = [e.stderr, e.stdout, e.message].map(v => typeof v === "string" ? v : "").join("\n");
console.log("e.status =", e.status); // -> 36 (NOT 1060, truncated by Bun)
console.log("/FAILED 1060/i:", /FAILED 1060/i.test(text)); // -> false (pt-BR says "FALHA 1060")
console.log("/\\b1060\\b/:", /\b1060\b/.test(text)); // -> true
}Output on a pt-BR host:
e.status = 36
/FAILED 1060/i: false
/\b1060\b/: true
Proposed fix
Match the locale-independent numeric code anywhere in the captured sc.exe output. The string 1060 is present regardless of UI language, and this is immune to the Bun exit-code truncation. In src/lib/winsw.ts, probeScmRegistration():
- if (e.status === 1060 || /FAILED 1060/i.test(text)) return false;
+ // 1060 = ERROR_SERVICE_DOES_NOT_EXIST. Two portability gaps make the original
+ // check fail:
+ // * Bun on Windows truncates the exit code to 8 bits, so a 1060 exit arrives
+ // as `e.status === 36` (1060 % 256) and the numeric guard never matches.
+ // * sc.exe localizes its output (e.g. pt-BR "FALHA 1060"), so /FAILED 1060/i
+ // never matches on non-English hosts.
+ // The numeric code itself is locale-independent and survives truncation, so
+ // match it anywhere in the captured stdout/stderr/message.
+ if (e.status === 1060 || /\b1060\b/.test(text)) return false;This keeps the fail-closed behavior for genuinely unreadable results (access denied, sc.exe missing, etc.) while correctly proving absence when the SCM reports 1060 in any locale.
Impact / notes
- Blocks
ocx serviceinstall on non-English Windows even with an elevated shell (the abort happens in the SCM-detection step, beforeschtasks). - Also affects
ocx updatewhen a background service is installed: its restart step callsocx service install, which hits the same abort. - After applying the one-line change locally,
ocx serviceproceeds toschtasks /createand installs the Task Scheduler backend as expected.
Happy to open a PR with the change if preferred.
Activity
fix(service): locale-safe sc.exe 1060 detection + tri-state lifecycle…
lidge-jun commentedon Jul 21, 2026
v2.7.31에 수정 반영했습니다. locale에 의존하지 않는 Win32 1060 감지와 서비스 lifecycle fail-closed 처리를 적용했고, Windows 포함 CI를 통과했습니다. 반영 커밋: fbd96c1
fix(service): locale-safe sc.exe 1060 detection + tri-state lifecycle…