Repository navigation
Description
Client or integration
Other
Area
Platform (Windows / macOS / Linux)
Summary
On Windows, installing the 2.92.0 desktop app on a machine that already runs the proxy from the npm package under the Task Scheduler service leaves the app stuck at state: taking-over. The takeover is refused ("the guarded stop could not confirm the approved runtime"), and nothing the app or the CLI prints tells the user how to get out of that state.
I understand from #5443 that refusing a takeover whose subject no longer matches is intended. The defect I am reporting is the dead end around it: the refusal is not recoverable through the documented CLI verbs, and the one thing that did work (freeing the port) is not suggested anywhere.
Expected: either the app attaches to the healthy runtime it cannot take over, or the refusal names a concrete next step (for example "stop the service, then relaunch"), and ocx stop / ocx service stop actually free the port when the service is the owner.
Reproduction
Starting state: @bitkyc08/opencodex 2.92.0 installed globally with npm, running under the Task Scheduler service opencodex-proxy (task registered elevated), serving 127.0.0.1:10100, with a populated ~/.opencodex home.
- Install
OpenCodex-2.92.0-windows-x64.msiand launchopencodex-desktop.exe. - The app stays at
state: taking-overwith the diagnostics below (first run, elapsed ~50 s). - Run the bundled
ocx.exe service repair. It reports "repaired and serving on port 10100" and rewrites the service wrapper to launch the bundledocx.exe, but the old npmbun.exeprocess keeps the port. The new wrapper instances spawned no child while the old process was alive (my inference: they could not openservice.logfor append, which the old process held). - Run
ocx.exe stop, thenocx.exe service start, thennpm uninstall -g @bitkyc08/opencodex, then restart the app. The runtime is now the bundledocx.exeunder the service, yet the app still stops attaking-overwith the same reason (second run, elapsed ~34 s). - With the service-owned runtime up,
ocx.exe service stopfails:
service stop did not take effect: a proxy is still listening on port 10100.It adds that native Codex was not restored. ocx.exe stopprints "Service manager stopped." and "Proxy (PID …) stopped.", but the service wrapper (cmd.exe /c opencodex-service.cmd) survives and relaunchesocx.exe start --port 10100within seconds. The port is never free, so the app keeps hitting the takeover path.- Killing the wrapper tree by hand (
taskkill /T /F /PID <wrapper>) frees the port. Relaunching the app then makes it start its own runtime as a child process (opencodex-desktop.exe->ocx.exe start --port 10100),/healthzis ok, and the app works. No takeover is attempted.
Version
2.92.0 (desktop MSI and npm package, same version)
Operating system
Windows 11 Pro 10.0.26100
Logs or error output
OpenCodex desktop 2.92.0 on windows
state: taking-over
reason: the guarded stop could not confirm the approved runtime: The approved runtime or managing CLI changed: the tracked runtime record could not be verified; no stop was attempted.
elapsed: 49962ms
endpoint: http://127.0.0.1:10100
home: C:\Users\<user>\.opencodex
start at login: turned on for this installation
installation id: <redacted>
runtime process: still running or never started
runtime output: none
--- second run, after the service pointed at the bundled ocx.exe ---
elapsed: 33668ms
start at login: already decided on this installation, left as the user set it
(other fields unchanged)
> ocx service stop
service stop did not take effect: a proxy is still listening on port 10100.
Native Codex was NOT restored, because doing so while the proxy is running leaves both pointing at each other. Check for a second service backend (`ocx service status`) or a manually started proxy, then re-run `ocx service stop`.
> ocx stop
Service manager stopped.
Proxy (PID <pid>) stopped.
Codex config restored from opencodex journal. ...
(a new ocx.exe start --port 10100 child of the surviving wrapper was listening on 10100 a few seconds later)Screenshots and supporting files
None.
Redacted configuration
Not configuration dependent. Per-user identifiers, the install id and the attestation secret were removed from everything above.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Oct 27, 2026
Status update: #7797 merged to
devas 41bd7e4. It hardens one part of this area: beforeocx ensurestarts a CLI runtime, supported bundled Linux/macOS and protected Debian installs now verify a live native Desktop recovery endpoint against OS process and socket evidence, so a process name alone no longer grants recovery authority. On Windows it grants no positive recovery credit and keeps the existing bounded wait for a fresh durable claim.It does not establish a fix for what this issue reports on Windows: the Desktop app stuck at
taking-overwhen an npm-installed runtime runs under the elevated Task Scheduler service, the refusal not naming a next step, andocx stop/ocx service stopnot freeing the port. Leaving this issue open for that remaining work.lidge-jun commentedon Oct 28, 2026
Design proposal for the remaining Windows dead end (source-backed against
devatfdc6dd82d; not a reproduction of the reporter's elevated Desktop session).What the source shows
buildWindowsServiceScriptemits a:loopthat relaunches Bun/ocx.exeabout five seconds after any exit except the protocol exit 42 (src/service/windows-taskxml.ts:98-134,src/service/windows-wrapper-exit.ts:1-8). That explains the respawn within seconds in step 6 without any Task Scheduler restart. The task XML also requests restart-on-failure and logon/session triggers (windows-taskxml.ts:251-281).schtasks /Endis already there.ocx service stopreachesstopWindows, which runs/end /tn opencodex-proxyand swallows non-benign errors (src/service/windows-ops.ts:843-851). Unlike plainocx stop, that path never calls the wrapper cleanup (src/service/orchestration.ts:39-48,:194-233). Plainocx stopdoes attempt wrapper cleanup, but the cleanup skips processes whose command line it cannot read, suppressesStop-Processerrors and ignores the PowerShell result (src/lib/windows-service-wrappers.ts:36-78). So neither verb can prove the wrapper ended.readPid()orreadRuntimePort(pid)returns null (src/cli/index.ts:1023-1028,src/cli/stop-approval.ts:117-143).readPid()needs a readable, validocx.pid, a live process and a recognisedstartcommand line from WMIC/CIM, and unavailable process metadata fails closed (src/config/process-state.ts:204-232,:308-351). An elevated target whose command line the unelevated Desktop cannot read is a plausible cause, but it is not proven. Same-account vs different-account elevation, a different home, an unreadable or PID-mismatched record and missing metadata all produce the same message.desktop/src-tauri/src/runtime_stop.rs:181-185,desktop/src-tauri/src/startup.rs:1311-1324). The Desktop: state the acceptance conditions for taking over an existing package runtime and for reusing consent #5443 consent boundary should stay as it is.Options
ocx stopandocx service stop(manager, wrapper and child, each bound and verified)Recommendation: A first, then B with native acceptance. For B, reuse the manager-binding observations in
src/service/windows-guarded-manager.ts: validate the registered task's action, principal and home, bind the live wrapper and child identities, and revalidate immediately before mutating anything. Report denied, unknown and survived as explicit outcomes, and keep client configuration until termination is proven. A task name, process name, port or path token alone is not enough authority to kill a process, so no blankettaskkill /T /Fguidance and no automatic elevation from Desktop.Evidence that would settle the cause (redacted; please do not post
runtime-port.json, tokens, environment dumps or full command lines): from the unelevated Desktop account and from an elevated terminal of the same account, the effective OpenCodex home, whetherocx.pidis readable, whetherocx service statusshows the task withHighestAvailable, and whetherGet-CimInstance Win32_Process -Filter "ProcessId=<pid>"returns aCommandLinefor the proxy PID. The full exit status (or--jsonoutput) of the plainocx stopfrom step 6 would also help.Test plan: unit regressions for each missing-evidence cause (refusal preserved, zero mutations, guidance never claims ownership). For B, a disposable native registered task covering:
/Endsucceeds but the wrapper survives; the child exits 0 and restarts; access denied; unreadable metadata; foreign same-name task; PID reuse. Acceptance also needs npm Bun vs bundledocx.exe, and an elevated service with unelevated Desktop.No safe narrow lifecycle slice is proven yet, so nothing is implemented in this round.
lidge-jun commentedon Oct 30, 2026
Progress from PR #7973, merged into
devasdb7bdc5e3d233aed8425420e77fbd66fe20ec947. This covers diagnostics A only: a guarded stop that refuses because the tracked PID or runtime record is absent, unreadable, malformed or inconsistent now says which fixed reason applied, and no paths, command lines or stderr appear in the detail.The rest of this issue is still open.
ocx stop/ocx service stopstill do not free the port when a surviving Task Scheduler wrapper relaunches the proxy, and the Desktop takeover dead end has no recovery step yet. This issue stays open for that work.kevin3669 commentedon Nov 2, 2026
Additional observation on npm 2.96.0: restart reported success while the old serving process survived
Related to the remaining Windows wrapper-lifecycle problem here; this does not claim to reproduce the Desktop “taking-over” state.
Environment: Windows 11, OpenCodex npm
2.96.0, bundled Bun1.4.4, Task Scheduler service.Observed sequence
ocx service restartfrom an environment carrying the intended proxy values./healthzand inspect identity-checked process metadata: the original serving PID, parent wrapper, and process start time remain unchanged. A newly launched wrapper also exists. No real PIDs or command lines are published here.The practical effect was that proxy changes on disk did not initially reach the serving runtime while synthetic
429 upstream_reset_replay_refusederrors continued. Transport details are in the companion follow-up to #7720. The lifecycle observation does not prove the root cause of every connection reset.Expected / request for next-release triage
Please assess whether the next release can verify the intended replacement runtime and wrapper ownership before reporting restart/repair success. A stale healthy listener should be reported explicitly as “old runtime still serving; requested replacement not confirmed”, rather than establishing successful activation. The existing ownership/consent boundaries and graceful handling of active requests should remain intact.
A useful regression would cover a registered task restart whose previous wrapper/child survives: verify that success is withheld until the intended runtime is observed, or return a bounded, content-free failure reason. Please avoid blanket process-name/port-based killing or unsafe elevation.
No personal/account/usage details, activity timestamps, PIDs, request IDs, IPs, paths, credentials, raw process command lines, or full logs are attached.
github-actions commentedon Nov 2, 2026
Automated translation bookkeeping — detected language: English.
Ingwannu commentedon Nov 4, 2026
Thanks @kevin3669. The 2.96.0 observation matches current
dev(72e80d280d) by static reading.ocx service restart/repairstops the service only withschtasks /end, and non-benign errors are swallowed (src/service/repair.ts:259->src/service/windows-ops.ts:843-851). It then calls/run(repair.ts:378). Nothing checks that the previous wrapper and its child exited.confirmServiceServingonly asks whether any OpenCodex healthz answers on the port (src/service/health.ts:73). A surviving old listener satisfies it, sohealth.ts:160prints "service restarted and serving" without a replacement.This has the same root cause as the remaining part of this issue (the wrapper survives
/end, and its:looprelaunches the child). The false success report can be fixed on its own without any new kill or elevation authority. Forrestart, record the healthzpidbefore the restart and withhold success until a different runtime answers. If the same runtime is still answering at the deadline, report "old runtime still serving; requested replacement not confirmed" and exit non-zero. Regression coverage would go intests/service/service.test.tswith an injected probe. Tracking that as a narrow slice here; the checked Windows stop that actually frees the port (Option B above) remains open.docs(upstream): triage closed PRs lidge-jun#7614/lidge-jun#7618, issue …