Client or integration
ocx CLI (Windows, Task Scheduler backend)
Area
Service lifecycle
Summary
windowsTaskRegistrationHealthy() compares the registered task XML against an XML-escaped <Arguments> string, but Windows Task Scheduler exports that element unescaped. The two can never be equal, so a healthy scheduler task is permanently reported as stale.
This produces the exact same user-facing symptom as the already-fixed #432, which makes it easy to mistake for a regression or duplicate — but the root cause is different and #432's fix does not cover it:
Consequence: stale is latched true → viable and startable are both false → ocx stops treating the scheduler backend as usable and falls back to launching the proxy directly. Re-running ocx service install elevated cannot clear it, because a freshly registered task exports the same way.
Root cause (code level, v2.7.42)
src/service.ts:
-
taskXmlString() escapes " → ":
function taskXmlString(value: string): string {
return value
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """) // <-- here
.replace(/'/g, "'");
}
-
The generated task XML therefore contains <Arguments>/b /nologo "...vbs"</Arguments>.
-
windowsTaskRegistrationHealthy() requires the registered XML to contain that same escaped form:
&& action.includes(`<Arguments>${taskXmlString(`/b /nologo "${launcher}"`)}</Arguments>`);
-
But " is a legal literal character inside XML element content, so Windows canonicalizes it on export. schtasks /query /xml returns:
<Arguments>/b /nologo "C:\path\to\opencodex-service-launcher.vbs"</Arguments>
Since the registered XML never contains ", the includes() check is unconditionally false whenever the launcher path is quoted — which it always is.
Reproduction
Windows 11 Pro (26200), @bitkyc08/opencodex 2.7.42, Bun 1.3.14, Task Scheduler backend, task installed and running normally.
-
Confirm the proxy is actually healthy:
curl.exe -s http://127.0.0.1:10100/healthz
# {"status":"ok","service":"opencodex","version":"2.7.42",...}
-
Confirm the scheduled task exists and is enabled:
Get-ScheduledTask -TaskName opencodex-proxy | Select-Object TaskName,State
# opencodex-proxy Ready
-
Show that the registered XML contains no escaped quote:
$raw = & "$env:SystemRoot\System32\schtasks.exe" /query /tn opencodex-proxy /xml ONE | Out-String
$raw.Contains('"') # False
$raw -match '<Arguments>[^<]*\x22' # True (literal " present)
-
Observe the false positive:
ocx status
# Service: installed, stale or missing service assets — run 'ocx service install' to repair
Note that bakedServicePathsDiagnostic() returns null here (both bunPath and cliPath exist, and the diagnostics string shows only logs: ... with no STALE baked paths prefix), and recordedBackend is scheduler, so staleBakedPaths and backendStateMismatch are both false — the stale flag can only be coming from schedulerInstalled && !schedulerAssetsHealthy, i.e. registrationHealthy === false.
Expected
A task whose registered <Arguments> is semantically identical to the generated one (differing only in XML escaping of characters that do not require escaping) is considered healthy.
Actual
registrationHealthy is false → stale is true → viable/startable false → scheduler backend treated as unusable; ocx update also attempts a service reinstall each time and reports the service as needing repair.
Suggested fix
Compare decoded text rather than raw substrings — unescape both sides (or at minimum normalize "/'/&) before the includes() check, in the same spirit as #432's "canonicalization-tolerant comparison". A regression test asserting that a task XML round-tripped through schtasks (literal ") still validates would prevent this class from recurring.
For context, the same canonicalization class has now bitten this function twice — once by element omission (#432), once by text escaping (this issue). It may be worth comparing against a parsed representation instead of doing string containment on raw XML.
Workaround
None that clears the flag. The proxy itself is unaffected and the logon trigger still works; only the health reporting and the scheduler-backend viability logic are wrong. Users can ignore the warning, but ocx will keep falling back to direct proxy launch instead of using the installed service.
Client or integration
ocxCLI (Windows, Task Scheduler backend)Area
Service lifecycle
Summary
windowsTaskRegistrationHealthy()compares the registered task XML against an XML-escaped<Arguments>string, but Windows Task Scheduler exports that element unescaped. The two can never be equal, so a healthy scheduler task is permanently reported as stale.This produces the exact same user-facing symptom as the already-fixed #432, which makes it easy to mistake for a regression or duplicate — but the root cause is different and #432's fix does not cover it:
<Enabled>,<RunLevel>) on export. Fixed by treating absence as the schema default (taskXmlOptionalValueEquals)."becomes a literal". The equality check is a raw substring match, so it fails regardless of the fix(windows): startup safety falsely marks healthy scheduler service stale #432 fix.Consequence:
staleis latched true →viableandstartableare both false →ocxstops treating the scheduler backend as usable and falls back to launching the proxy directly. Re-runningocx service installelevated cannot clear it, because a freshly registered task exports the same way.Root cause (code level, v2.7.42)
src/service.ts:taskXmlString()escapes"→":The generated task XML therefore contains
<Arguments>/b /nologo "...vbs"</Arguments>.windowsTaskRegistrationHealthy()requires the registered XML to contain that same escaped form:But
"is a legal literal character inside XML element content, so Windows canonicalizes it on export.schtasks /query /xmlreturns:Since the registered XML never contains
", theincludes()check is unconditionally false whenever the launcher path is quoted — which it always is.Reproduction
Windows 11 Pro (26200),
@bitkyc08/opencodex2.7.42, Bun 1.3.14, Task Scheduler backend, task installed and running normally.Confirm the proxy is actually healthy:
Confirm the scheduled task exists and is enabled:
Show that the registered XML contains no escaped quote:
Observe the false positive:
ocx status # Service: installed, stale or missing service assets — run 'ocx service install' to repairNote that
bakedServicePathsDiagnostic()returns null here (bothbunPathandcliPathexist, and the diagnostics string shows onlylogs: ...with noSTALE baked pathsprefix), andrecordedBackendisscheduler, sostaleBakedPathsandbackendStateMismatchare both false — thestaleflag can only be coming fromschedulerInstalled && !schedulerAssetsHealthy, i.e.registrationHealthy === false.Expected
A task whose registered
<Arguments>is semantically identical to the generated one (differing only in XML escaping of characters that do not require escaping) is considered healthy.Actual
registrationHealthyis false →staleis true →viable/startablefalse → scheduler backend treated as unusable;ocx updatealso attempts a service reinstall each time and reports the service as needing repair.Suggested fix
Compare decoded text rather than raw substrings — unescape both sides (or at minimum normalize
"/'/&) before theincludes()check, in the same spirit as #432's "canonicalization-tolerant comparison". A regression test asserting that a task XML round-tripped throughschtasks(literal") still validates would prevent this class from recurring.For context, the same canonicalization class has now bitten this function twice — once by element omission (#432), once by text escaping (this issue). It may be worth comparing against a parsed representation instead of doing string containment on raw XML.
Workaround
None that clears the flag. The proxy itself is unaffected and the logon trigger still works; only the health reporting and the scheduler-backend viability logic are wrong. Users can ignore the warning, but
ocxwill keep falling back to direct proxy launch instead of using the installed service.