Client or integration
CLI / Updater / Process Control
Area
Installation or packaging
Summary
When ocx update attempts to stop the running proxy before replacing packages, it records a shared teardown claim via claimPendingTeardown (~/.opencodex/pending-teardown-<nonce>.json). If the stop or drain sequence is interrupted (e.g. timeout, service kill, watchdog kickstart, or external SIGTERM/SIGHUP), the process exits before reaching clearPendingTeardown(nonce).
Because bin/ocx.mjs checks hasPendingTeardownIn(readdirSync, configDir()) without validating process liveness (isProcessAlive(ownerPid)), any subsequent run of ocx update immediately aborts with:
opencodex: a shared teardown from an earlier stop is still outstanding and needs manual review; aborting the update.
opencodex: confirm no proxy is running, run 'ocx restore', then remove the pending-teardown file in the opencodex home.
If multiple attempts are made while the service is unstable, each attempt claims a new receipt and fails to clean it up. In our environment, this caused 6 orphaned pending-teardown-*.json receipts to accumulate, wedging the updater until files were manually deleted.
Reproduction
- Run
opencodex as a managed service on macOS (gui/<uid>/com.opencodex.proxy).
- Trigger an update via
ocx update --tag latest while the proxy is under load or handling long requests.
- Interrupt or kill the update worker during the stop phase before the final
clearPendingTeardown() call.
- The worker process (ownerPid) exits. The receipt
pending-teardown-<nonce>.json remains in ~/.opencodex/.
- Run
ocx update again.
- Observe:
opencodex v2.56.0 (installed via npm, tag latest)
Verified @bitkyc08/opencodex@2.57.0 integrity metadata sha512-lKEPsKq7DYSg2AhA6…
opencodex: a shared teardown from an earlier stop is still outstanding and needs manual review; aborting the update.
opencodex: confirm no proxy is running, run 'ocx restore', then remove the pending-teardown file in the opencodex home.
- The updater refuses to proceed due to
teardown-outstanding, even though the recorded ownerPid is dead and no proxy is running.
Version
2.56.0 -> 2.57.0
Operating system
macOS 27.0 (Darwin 27.0.0 arm64)
Provider and model
Not provider-specific
Logs or error output
opencodex: a shared teardown from an earlier stop is still outstanding and needs manual review; aborting the update.
opencodex: confirm no proxy is running, run 'ocx restore', then remove the pending-teardown file in the opencodex home.
In update-job.json:
{
"currentVersion": "2.56.0",
"latestVersion": "2.57.0",
"exitCode": 1,
"error": "update command failed (1)",
"log": [
"exit 1 · 505 bytes of output withheld (no recognized diagnostic fields)"
]
}
Orphaned receipt files in ~/.opencodex/:
pending-teardown-75280d7e4be191581141ca7863fbc7aa.json
pending-teardown-756b6d207bdfc07862a7969abb7b2c39.json
pending-teardown-a8d6f0e0c1d042ff281c49b7962265bb.json
pending-teardown-b13be77f97d4b87e0f0b6160279dc2b4.json
pending-teardown-b3bee9495dd4b13b3414c3c2013e1281.json
pending-teardown-f9f3728779188403ef2e9179778a0d04.json
Receipt payload example:
{
"ownerPid": 90028,
"nonce": "b3bee9495dd4b13b3414c3c2013e1281",
"createdAt": "2026-09-17T08:52:45.393Z",
"endpoint": { "hostname": "127.0.0.1", "port": 10100 },
"endpointSource": "exact"
}
Root cause analysis
- Lack of exit/signal rollback: In
src/cli/index.ts, claimPendingTeardown drops a durable receipt on disk before stopping the proxy. If stopProxy or eviction fails or is aborted, there is no signal handler or try...finally block to prune the uncommitted claim.
- Stat-only check in update launcher:
hasPendingTeardownIn in bin/ocx.mjs only matches the filename pattern pending-teardown-*.json. It does not inspect the receipt payload or verify whether ownerPid is still alive via isProcessAlive(receipt.ownerPid).
- Deadlock accumulation: When
bin/ocx.mjs sees an existing receipt, it invokes ocx stop to settle it, which claims yet another receipt before verifying the inherited ones. An interrupted chain easily snowballs into multiple orphaned files.
Suggested fix
- In
bin/ocx.mjs / hasPendingTeardownIn: Parse existing receipts and check if ownerPid is still active. If isProcessAlive(ownerPid) is false and the target port is dead, the abandoned receipt should either be auto-recovered/cleared or safely ignored for the update gate.
- In
src/cli/index.ts: Wrap stopWithDeferral with cleanup hooks so that if the process terminates unexpectedly before shared teardown completes, the uncommitted receipt is retracted.
Checks
Client or integration
CLI / Updater / Process Control
Area
Installation or packaging
Summary
When
ocx updateattempts to stop the running proxy before replacing packages, it records a shared teardown claim viaclaimPendingTeardown(~/.opencodex/pending-teardown-<nonce>.json). If the stop or drain sequence is interrupted (e.g. timeout, service kill, watchdog kickstart, or external SIGTERM/SIGHUP), the process exits before reachingclearPendingTeardown(nonce).Because
bin/ocx.mjscheckshasPendingTeardownIn(readdirSync, configDir())without validating process liveness (isProcessAlive(ownerPid)), any subsequent run ofocx updateimmediately aborts with:If multiple attempts are made while the service is unstable, each attempt claims a new receipt and fails to clean it up. In our environment, this caused 6 orphaned
pending-teardown-*.jsonreceipts to accumulate, wedging the updater until files were manually deleted.Reproduction
opencodexas a managed service on macOS (gui/<uid>/com.opencodex.proxy).ocx update --tag latestwhile the proxy is under load or handling long requests.clearPendingTeardown()call.pending-teardown-<nonce>.jsonremains in~/.opencodex/.ocx updateagain.teardown-outstanding, even though the recordedownerPidis dead and no proxy is running.Version
2.56.0 -> 2.57.0
Operating system
macOS 27.0 (Darwin 27.0.0 arm64)
Provider and model
Not provider-specific
Logs or error output
In
update-job.json:{ "currentVersion": "2.56.0", "latestVersion": "2.57.0", "exitCode": 1, "error": "update command failed (1)", "log": [ "exit 1 · 505 bytes of output withheld (no recognized diagnostic fields)" ] }Orphaned receipt files in
~/.opencodex/:Receipt payload example:
{ "ownerPid": 90028, "nonce": "b3bee9495dd4b13b3414c3c2013e1281", "createdAt": "2026-09-17T08:52:45.393Z", "endpoint": { "hostname": "127.0.0.1", "port": 10100 }, "endpointSource": "exact" }Root cause analysis
src/cli/index.ts,claimPendingTeardowndrops a durable receipt on disk before stopping the proxy. IfstopProxyor eviction fails or is aborted, there is no signal handler ortry...finallyblock to prune the uncommitted claim.hasPendingTeardownIninbin/ocx.mjsonly matches the filename patternpending-teardown-*.json. It does not inspect the receipt payload or verify whetherownerPidis still alive viaisProcessAlive(receipt.ownerPid).bin/ocx.mjssees an existing receipt, it invokesocx stopto settle it, which claims yet another receipt before verifying the inherited ones. An interrupted chain easily snowballs into multiple orphaned files.Suggested fix
bin/ocx.mjs/hasPendingTeardownIn: Parse existing receipts and check ifownerPidis still active. IfisProcessAlive(ownerPid)is false and the target port is dead, the abandoned receipt should either be auto-recovered/cleared or safely ignored for the update gate.src/cli/index.ts: WrapstopWithDeferralwith cleanup hooks so that if the process terminates unexpectedly before shared teardown completes, the uncommitted receipt is retracted.Checks