Repository navigation
Windows scheduler backend: ocx service stop reports success without stopping the proxy, and the --native switch breaks the existing backend #764
Description
Activity
github-actions commented
on Jul 30, 2026 on Jul 30, 2026 – with GitHub ActionsContributorMore actionsIssue reopened
The report now contains the information required by the automated check. Thanks for updating it.
github-actions commented
on Jul 30, 2026 on Jul 30, 2026 – with GitHub ActionsContributorMore actionsAutomated translation bookkeeping — detected language: unknown.
- changed the title
[-]Windows: issues that force downstream patching (service stop, management token fail-closed, native backend, startup probes)[/-][+]Windows scheduler backend: ocx service stop reports success without stopping the proxy, and the --native switch breaks the existing backend[/+]on Jul 30, 2026 [shipping-github] Research review
Claim: Windows scheduler
ocx service stopreports success without stopping the proxy;--nativeswitch can brick the scheduler backend.
Checked against: dev@be177ea5Field Finding Still an issue on latest development? yes Exact issue stopWindowsonlyschtasks /end;stopTrackedProxyIfRunningreturns early if pid file missing;uninstallWindowsdeletes launcher files even if task delete failed; WinSW/pinteractive path unchangedFixed on development? no Open PR covering this? none for stop/orphan (open #736 is locale-independent scheduler status) Duplicate of? none (related Windows service issues #722/#608/#733 are distinct) Security relevance none — n/a Priority high — stop lies about success; failed --nativecan leave a task pointing at deleted launchersVerdict Needs fix on development Evidence:
src/service.tsstopWindows~1401;stopTrackedProxyIfRunning~1564–1566 (if (!pid) return "none");uninstallWindowsdeletes scripts after best-effort/delete. Matches reporter's multi-part write-up.
Gaps: Did not reproduce on a live Windows scheduler install in this session.[shipping-github] Opened PR #780 to address this.
The first half is fixed on
codex/260731-pr-merge-round(2d0c6a99c+5530b8c63); the--nativeswitch half is untouched.Why stop reported success.
ops.stop()reports the outcome of the stop command, not of the process. A scheduler wrapper that survivesschtasks /endrespawns its child a few seconds later, so a stop that returned success can still leave a live proxy. The tracked-pid cleanup does not catch it either — the respawned child writes a pid this process never recorded, and the cleanup returns "none" the moment the pid file is absent.What changed. Stop is now verified rather than inferred. After the manager and the tracked proxy are stopped, the command polls for a live proxy across the observed restart window; if one answers it reports the port, leaves native Codex alone, and exits non-zero. Restoring the config on top of a running proxy is what turns a failed stop into a broken install, so that is the step that must not run on an unverified stop.
Deliberately different from PR #780. That PR waits only when
schtasks /endreturns an error. Your report is an/endthat succeeds while the wrapper lives — on that path there is nothing for the command to report, so waiting on its exit status cannot help. #780's other work (orphan discovery, PID verification, Microsoft-account preflight) is untouched and still worth reviewing separately.One correction I had to make. The first version ran the full wait on every stop, on every platform. Review caught it, and it was right to: launchd and systemd do not bring a child back after unload/stop, so making every macOS and Linux user wait out a Windows-specific window would have been a worse everyday bug than the one being fixed. The wait is now scoped to supervisors that can actually respawn, with tests pinning both the fast path and the platform derivation itself.
Staying open for the
--nativeswitch, which is a separate defect in the same command.- added a commit that references this issue
on Jul 31, 2026 - added 2 commits that reference this issue
on Jul 31, 2026 24 remaining items
- added 14 commits that reference this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 21, 2026
Client or integration
OpenCodex dashboard
Area
Service lifecycle
Summary
On Windows with the
schedulerbackend,ocx service stopprints a success message without stopping the proxy. There are two independent causes, plus three related problems in the--nativeswitch that I hit while investigating.1a.
stopWindows()never reaches the wrapper. The scheduled task runswscript.exe->opencodex-service-launcher.vbs->opencodex-service.cmd.schtasks /enddoes not terminate that wrapper here, and the wrapper's:looprespawns the proxy ~5s after anything stops the child, so an explicit stop is silently undone.stopServiceIfInstalled()also returnstrueas soon as the task exists, without verifying the proxy died.1b.
stopTrackedProxyIfRunning()has no orphan recovery. It gives up when the pid file is missing or stale:ocx stophandles this viafindLiveProxy(); the service path does not. So a lost pid file turnsocx service stopinto a no-op that reports success.1c.
findLiveProxy()'s identity probe has a fixed 750ms budget.proxyIdentityAt()usesAbortSignal.timeout(io.timeoutMs ?? 750). Against a proxy that has only just begun listening, this misses a proxy that is plainly alive — I measured the same call succeeding 5/5 in isolation and failing intermittently when invoked from the stop path. Every caller treatsnullas "no proxy", so the failure is silent.2.
uninstallWindows()deletes files before verifying the task was removed.Without elevation
schtasks /deletefails silently, the three artifacts are deleted anyway, andinstallWindowsNative()then correctly aborts withTask Scheduler backend still present after removal. The abort guard is right, but the surviving task now points at a launcher that no longer exists, so the scheduler backend is broken and fails at next logon.3.
install --nativecannot work for Microsoft-account users, and fails at the very end. The generated WinSW XML uses<domain>HOSTNAME</domain><user>username</user>withallowservicelogon. For an account whosePrincipalSourceisMicrosoftAccount,sc createcannot authenticate it. The user is prompted for a password that can never work, and by then the scheduler backend has already been removed per problem 2.4.
install /phangs indefinitely without an interactive console.runWinswInteractive()usesstdio: "inherit"so WinSW can prompt for the password. Invoked from a hidden or non-interactive parent, the prompt is invisible and the process waits forever — I had one sit at 0% CPU for over five minutes with no output and no timeout.5. A Windows service cannot see user-level environment variables. Related, because setting
OPENCODEX_ADMIN_AUTH_TOKENis the documented-in-code escape hatch for a separate problem: the scheduler backend inherits the logon-time environment block (a freshly set user variable is invisible until the next logon), and the WinSW backend declares a fixed<env>list that does not include it. PassingOPENCODEX_ADMIN_AUTH_TOKENandOPENCODEX_ACL_TIMEOUT_MSthrough to the generated XML alongsideCODEX_HOME/OPENCODEX_HOMEwould close this.Reproduction
Problem 1 (stop is a no-op):
ocx service start. Confirm the proxy answers on/healthz.%USERPROFILE%\.opencodex\ocx.pidand write a staleruntime-port.jsonpointing at a dead pid and an unused port. (This state also arises naturally whenever the pid file is lost while the proxy is alive.)ocx service stop. It printsservice stopped + native Codex restored.Even with a coherent pid file, stopping only the child lets the wrapper respawn it about five seconds later.
Problems 2-4 (
--nativeswitch):ocx service install --native. It aborts withTask Scheduler backend still present after removal— butopencodex-service.cmd,opencodex-service-launcher.vbsandopencodex-service-task.xmlare already gone.FATAL - Failed to create service. The account name is invalid or does not exist, or the password is invalid for the account name specified.Start-Process -Verb RunAs -WindowStyle Hidden) and it hangs at the invisible password prompt indefinitely.Version
2.7.43
Operating system
Windows 11 (build 10.0.26200),
@bitkyc08/opencodexinstalled vianpm -g,schedulerservice backend.Provider and model
Not provider-specific — this is service lifecycle only.
Logs or error output
Redacted configuration
service-state.json:{ "version": 2, "backend": "scheduler" }Scheduled task principal (this is why the scheduler backend works for Microsoft-account users and WinSW does not):
Suggested fixes
findLiveProxy()the wayocx stopalready does, terminate the wrapper when the scheduler backend is in use (guarded by matching the full wrapper script path so an unrelatedcmd.execannot match), and verify the listen port is actually free before reporting success.--nativepreflight and refuse with an explanation, before touching the existing backend.install /pprompt.I have working implementations of 1, 2 and 5 and am happy to send PRs.
Additional: two pre-existing test failures on a clean checkout
Both reproduce on a clean
v2.7.43with no local changes, on Windows:tests/service.test.ts->systemd service unit > uses unquoted append targets for service logstests/winsw.test.ts->winsw xml > registers the user service account (v2 schema), never LocalSystemFor the second the generated XML looks correct to me (
<domain>HOSTNAME</domain><user>username</user><allowservicelogon>true</allowservicelogon>matchingwhoami), so it may be a fixture assumption rather than a real defect.Separately,
tests/anthropic-image-normalize.test.tsrequiresBun.Image: it fails on bun 1.3.12 and passes on the 1.3.14 the repo pins. An engine check or skip guard would make that self-explanatory.Checks