fix(codex): route resume/fork through the canonical home and bound helper shutdown (#647) - #648
Conversation
…lper shutdown `mcodex resume`/`mcodex fork` hung on a blank TUI whenever runtime rotation was enabled. Both are interactive TUI entry points, but they carry a forwarded subcommand, so `isCodexInteractiveTuiCommand` (which only matches a bare invocation) missed them and they fell through to the shadow-home transport. The shadow mirror deliberately omits the runtime SQLite state, so the shadow session index only ever held a partial thread list rebuilt from the linked `sessions` directory and could not contain the requested thread. Classify `resume`/`fork` with a dedicated predicate and route them to the same canonical-home app-helper transport the bare TUI already uses, keeping account rotation enabled. Helper shutdown was also unbounded. `stopRuntimeRotationAppHelper` sent SIGTERM and stopped waiting after two seconds, but left the helper and its piped stdio referenced. Because the helper is spawned with piped stdio, those pipes kept the wrapper's event loop alive and the shell prompt never returned after an interrupted or nonzero Codex exit. Shutdown now escalates to SIGKILL past the graceful window and unconditionally destroys and unrefs the helper's streams — the part that actually frees the wrapper on Windows, where the signals are emulated as unconditional termination. The exit wait also removes its own `close` listener when the timeout wins. Closes #647 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0139f4WZCmWykXZcdEmWTusj
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Review limit reached
Next review available in: 15 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 Walkthroughmajor fix with no stated security or data-loss risk. regression tests in Walkthroughruntime rotation now routes interactive Changesruntime rotation lifecycle
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
📋 Issue PlannerBuilt with CodeRabbit's Coding Plans for faster development and fewer bugs. View plan used: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ansport Routing `resume`/`fork` to the interactive branch made their `--help`/`-h` form spawn a detached app helper. That branch detaches on a clean exit, which help always is, so the helper outlived the wrapper and idled for its full timeout. Before this PR the same invocation built a shadow home and a proxy, but both were torn down when the wrapper exited, so nothing was left behind. Short-circuit the transport for the help form instead, matching the existing `app --help` precedent in the same function: printing help makes no model requests, so it needs no proxy, no shadow home, and no helper. Scoped to the commands that spawn a detached helper, and keyed off the help flag rather than the command, so a real `resume <id>` still routes through rotation. Reported by Greptile on #648. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0139f4WZCmWykXZcdEmWTusj
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
test/codex-bin-wrapper.test.ts (1)
3128-3172: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winbound the subprocess tests.
the new shutdown regressions intentionally exercise helpers that keep stdio open or ignore
SIGTERM. the sharedrunWrapper()attest/codex-bin-wrapper.test.ts:644-656callsspawnSync()without a timeout. if cleanup regresses, Vitest hangs instead of reporting a failed assertion. add a timeout slightly above the documented shutdown bound and assert that the child did not time out.As per path instructions, tests must stay deterministic.
Also applies to: 3174-3232
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/codex-bin-wrapper.test.ts` around lines 3128 - 3172, Update the shared runWrapper() helper used by the shutdown regression tests to pass spawnSync() a timeout slightly longer than the documented shutdown window, then assert that the returned status is not the timeout indicator before existing status and elapsed-time assertions. Apply this protection to both affected subprocess tests while preserving their current deterministic expectations.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@test/codex-bin-wrapper.test.ts`:
- Around line 3128-3172: Update the shared runWrapper() helper used by the
shutdown regression tests to pass spawnSync() a timeout slightly longer than the
documented shutdown window, then assert that the returned status is not the
timeout indicator before existing status and elapsed-time assertions. Apply this
protection to both affected subprocess tests while preserving their current
deterministic expectations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: dca29391-6415-4c0d-ac00-2b19f7856af7
📒 Files selected for processing (2)
scripts/codex.jstest/codex-bin-wrapper.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,js,mjs}: Use ESM modules throughout the project; the package is configured with"type": "module".
Do not useas any,@ts-ignore, or@ts-expect-error.
Files:
scripts/codex.jstest/codex-bin-wrapper.test.ts
scripts/codex*.js
📄 CodeRabbit inference engine (AGENTS.md)
The wrapper must not reimplement general Codex commands; authentication commands are handled locally and non-authentication commands must forward to the official Codex CLI.
Files:
scripts/codex.js
scripts/**/*.js
📄 CodeRabbit inference engine (AGENTS.md)
Windows-sensitive cleanup and write operations must retry transient
EBUSY,EPERM, andENOTEMPTYfailures where applicable.
Files:
scripts/codex.js
**/*
📄 CodeRabbit inference engine (AGENTS.md)
Source changes belong in
index.ts,lib/, andscripts/;dist/is generated output and local temporary/cache directories must not be edited.
Files:
scripts/codex.jstest/codex-bin-wrapper.test.ts
**/*.{js,ts,mjs,cjs}
📄 CodeRabbit inference engine (README.md)
**/*.{js,ts,mjs,cjs}: Do not publish or replace a globalcodexbinary; official OpenAI installation paths must retain ownership of thecodexcommand.
Keep OAuth credentials local and restrict runtime rotation and local bridges to loopback interfaces.
Require hashed local client tokens to protect the optional loopback bridge.
Responsesbackground: truecompatibility must remain opt-in; requests using it must use statefulstore=truerouting rather than statelessstore=falserouting.
Never run npm install or update commands automatically; only display a manual upgrade notice when appropriate.
Experimental synchronization and backup flows must be non-destructive by default: preview before applying sync, preserve destination-only accounts, and fail safely on backup filename collisions.
Keep account storage project-scoped under the configured multi-auth root when operating in repo-specific workflows.
Files:
scripts/codex.jstest/codex-bin-wrapper.test.ts
test/**/*.test.ts
📄 CodeRabbit inference engine (test/AGENTS.md)
test/**/*.test.ts: Write Vitest test suites with globals enabled (describe, it, expect)
Maintain 80%+ coverage threshold across statements, branches, functions, and lines
Use removeWithRetry() for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY, EPERM, and ENOTEMPTY errors
Do not rely on dist/ in tests; use source files instead
Do not skip tests without justification
Relax lint rules for test files as configured in eslint.config.js
Files:
test/codex-bin-wrapper.test.ts
test/**/codex-bin-wrapper.test.ts
📄 CodeRabbit inference engine (test/AGENTS.md)
Test bin wrapper lazy-load and missing dist handling with concurrent invocations in codex-bin-wrapper.test.ts
Files:
test/codex-bin-wrapper.test.ts
test/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Windows-sensitive filesystem tests and helpers must use retry handling for transient lock-related cleanup and write failures.
Files:
test/codex-bin-wrapper.test.ts
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/codex-bin-wrapper.test.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:39.822Z
Learning: Runtime rotation must remain enabled by default, local, reversible, and loopback-only.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Resolve the runtime root directory in this order: `CODEX_MULTI_AUTH_DIR`; an explicit non-default `CODEX_HOME` at `$CODEX_HOME/multi-auth`; existing account-storage roots under `CODEX_HOME` or `~/.codex`; canonical `~/.codex/multi-auth`; and legacy paths only when storage signals exist.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Read `dashboardDisplaySettings` and `pluginConfig` from `settings.json`, while retaining legacy compatibility loading and migration.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Resolve `pluginConfig` values using this precedence: existing `CODEX_MULTI_AUTH_CONFIG_PATH` file, valid unified `settings.json` configuration, legacy compatibility configuration, and `DEFAULT_PLUGIN_CONFIG`; then apply environment-variable overrides per setting.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Ignore a configured but nonexistent `CODEX_MULTI_AUTH_CONFIG_PATH` during load, while creating it on the first save if the variable remains set. Resolve dashboard display values from persisted settings followed by normalization and defaults.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Resolve account storage by selecting the root directory, using the global accounts file by default, using a project-namespaced path when project-scoped mode is active, and attempting legacy project-file migration when applicable.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Normalize standalone and wrapper command forms before dispatch; run auth-manager commands locally, forward out-of-scope wrapper commands to the official Codex CLI, and check runtime rotation for forwarded request-bearing commands.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: For runtime rotation, honor `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` before `pluginConfig.codexRuntimeRotationProxy`, bypass rotation when disabled or for help/non-requesting commands, use a per-process token, select the appropriate canonical or shadow `CODEX_HOME` transport, forward to official Codex, rotate on relevant failures before streaming, and clean up or synchronize shadow state on exit.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: For plugin-host requests, transform requests for Codex compatibility, resolve candidates using health/cooldown/quota/affinity, apply timeout and retry policy, perform failover and rotation decisions, and persist account, cache, and session updates.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: For unsupported model or entitlement failures, record entitlement-cache state, penalize the account/model capability pair, apply fallback-model policy when enabled, and re-evaluate scoring and retries.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-08-02T07:54:48.418Z
Learning: Detect account-file updates with a watcher, debounce and reload the in-memory account manager, and keep session-affinity and guardian processes synchronized with the updated state.
📚 Learning: 2026-06-04T06:14:18.093Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/scheduling-strategy-config.test.ts:1-1
Timestamp: 2026-06-04T06:14:18.093Z
Learning: In ndycode/codex-multi-auth, do not flag explicit imports from "vitest" (e.g., describe, it, expect, beforeEach/afterEach, etc.) in test files as issues—even if the Vitest config sets `globals: true`. The repo’s established convention is to keep these imports for consistency with neighboring tests; removing them would make files outliers.
Applied to files:
test/codex-bin-wrapper.test.ts
📚 Learning: 2026-06-04T06:14:24.975Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/runtime-rotation-proxy.test.ts:2478-2491
Timestamp: 2026-06-04T06:14:24.975Z
Learning: In ndycode/codex-multi-auth test files (e.g. `test/*.test.ts`), when creating V3 storage fixtures for accounts, it’s an intentional convention to use `as never` for deliberately minimal stored-account objects that only include `refreshToken`, `addedAt`, and `lastUsed`. Do not treat `as never` here as a type-safety problem: optional/other fields are expected to be populated by the runtime during execution, and the cast is used solely to keep the fixture minimal and consistent across existing tests.
Applied to files:
test/codex-bin-wrapper.test.ts
🪛 ast-grep (0.45.0)
test/codex-bin-wrapper.test.ts
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🔇 Additional comments (4)
scripts/codex.js (2)
4006-4058: LGTM!
4210-4213: LGTM!Also applies to: 4417-4427, 4442-4445, 4471-4479
test/codex-bin-wrapper.test.ts (2)
3174-3232: 🩺 Stability & Availabilityverify the force-kill path on windows.
test/codex-bin-wrapper.test.ts:3174-3232is posix-only, but the wrapper also runs on windows. verify that theSIGTERMwait,SIGKILLescalation, child-exit detection, and stream cleanup inscripts/codex.js:4006-4058remain bounded on windows. add a windows case or an explicit platform-specific implementation if signal semantics differ.As per path instructions, tests under
test/**must cover windows-sensitive behavior.Source: Path instructions
311-311: LGTM!Also applies to: 360-396, 2930-3001, 3003-3049, 3051-3088, 3090-3126
…ead of stalling The two shutdown regressions deliberately drive a helper that keeps its stdio open or ignores SIGTERM. They drove the wrapper through the shared `runWrapper` helper, which calls `spawnSync` with no timeout. `spawnSync` blocks the worker thread, so Vitest's `testTimeout` cannot interrupt it: a shutdown regression hung the entire run rather than failing an assertion. That is not theoretical — it stalled a verification run of this branch against the unfixed wrapper. Give `runWrapper` an opt-in `timeoutMs` and assert `result.error` is unset before the status and elapsed checks. Only the two shutdown tests pass it, so every other test keeps its current unbounded behavior. Against the unfixed wrapper both now fail in ~12s with a message naming the cause instead of hanging. Reported by CodeRabbit on #648. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0139f4WZCmWykXZcdEmWTusj
|
Addressed in 9467b2f. Valid and worth fixing.
Verified against the unfixed wrapper on both platforms — each now fails in ~12s with a message naming the cause, instead of hanging: The second of those previously hung indefinitely. Full suite after the change: Windows 5284 passed / 0 failed; Linux 5263 passed with the same 19 failures the unmodified |
Extends the help short-circuit from `app`/`resume`/`fork` to `exec`/`review`, so all five request commands behave the same way. `exec --help` never stranded a helper the way the interactive commands could, but it did mirror an entire shadow Codex home and start a proxy purely to print help, then tear both down. The predicate is now just `requestCommands` plus a help flag, which removes the second command set and makes the rule one sentence: a request command that is only printing help makes no model requests, so it needs no transport. This also lines up with `app-server`, whose help has always short-circuited here. Still keyed off the help flag rather than the command, so real runs are untouched — the existing `exec`/`review` shadow-home tests continue to pass. Follow-up to the review discussion on #648. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0139f4WZCmWykXZcdEmWTusj
|
@coderabbitai review |
✅ Action performedReview finished.
|
A corrective release. No new features and no configuration changes. Patch rather than minor: 2.8.0 was minor because it changed where the official CLI keeps its state. This one only corrects behaviour that was already meant to work, adds no settings, and writes nothing new to disk — the same shape as 2.7.1. mcodex resume and mcodex fork hung on a blank TUI whenever runtime rotation was enabled. Both are interactive TUI entry points that carry a forwarded subcommand, so 2.8.0's interactive classification — which matched only an invocation with no subcommand — missed them and left them on the shadow home, whose mirror deliberately omits the runtime SQLite state. Both now use the canonical-home transport, with rotation still enabled. The wrapper could also fail to return to the shell after an interrupted or non-zero exit, because helper shutdown left the detached helper's pipes referenced. Shutdown is now bounded and releases those handles. Separately, --help no longer starts a rotation transport for any request command. Also clears four high-severity advisories that were failing npm run audit:ci: hono 4.12.21 -> 4.12.33 and undici 6.25.0 -> 6.28.0, plus brace-expansion and postcss pinned through overrides for the dev graph. Closes ndycode#647. Landed as ndycode#648, ndycode#649, and ndycode#650. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0139f4WZCmWykXZcdEmWTusj
`codex app-server` took the shadow-`CODEX_HOME` transport, and a resident server cannot live there. Codex applies an lstat-strict check to `<CODEX_HOME>/app-server-control` and refuses to start when it is a symlink, which is what the shadow mirror makes of it; and a server that does start hands every attached client a frozen snapshot of the thread index for the life of the process, writing anything it creates into a copy discarded at exit. Route it to the canonical-home app helper, alongside the interactive TUI and `resume`/`fork` (ndycode#647/ndycode#648). Do not install the app-server CLI shim on that branch. The shim is reachable only from the app helper, so moving the transport silently drags it in, and it stamps `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0`, `CODEX_CLI_PATH`, and a preload `NODE_OPTIONS` onto the environment the forwarded child inherits. Codex passes that environment to shell tools and MCP servers, so every nested wrapper invocation would read rotation as disabled and bill whatever account the official CLI resolved. Its only purpose is to intercept the desktop app spawning its own app-server; a wrapper-invoked server already carries the overrides on its command line. `codex app` and the interactive branches keep it, unchanged. Because no shim means no account-label env, request the `account/read` / `getAuthStatus` / `account/rateLimits/read` rewriting explicitly through `proxyAppServerAccountRead`. Also on the shared helper path: - Honor an explicit `detachOnExit: false`, so a short-lived app-server no longer strands its helper for the full 12h idle timeout. - Start the detach-grace clock when the helper is ready rather than before a launch bounded at 15s, so `codex app` cannot kill the helper it just handed the desktop app off to. - Turn a helper that cannot start into a diagnostic and exit 1 instead of an unhandled rejection with a leaked compatibility home. Hard-fail is deliberate: there is no rotation-off shape to degrade into, and a resident server that quietly loses rotation is a billing error you cannot see. - Stop accumulating the helper startup stdout/stderr buffers once startup settles; the listeners stay attached so the helper never blocks on a full pipe. Reported with a complete reproduction by Mike Bannister (@possibilities) in ndycode#659; the core routing change is from their ndycode#660. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KT5pNBtZ151FSC6KBA32sk
Summary
mcodex resume/mcodex forkhanging on a blank TUI when runtime rotation is enabled, by routing both through the canonical-home app-helper transport instead of the ephemeral shadow home.--helpstraight to the official CLI, so help never starts a proxy, a shadow home, or a helper.What Changed
Routing (
scripts/codex.js).resumeandforkare interactive TUI entry points, but they carry a forwarded subcommand, soisCodexInteractiveTuiCommand— which only matches a bare invocation — missed them and they fell through to the shadow-home proxy path. The shadow mirror deliberately omits the runtime SQLite state (isRuntimeRotationShadowHomeOmittedEntry), so a shadow home only ever holds a partial thread index rebuilt from the linkedsessionsdirectory, and the requested thread was simply absent. That matches the reporter's evidence exactly: canonicalstate_5.sqliteheld 257 threads including the target, while the two shadow databases held 126 and 128 and did not.A dedicated predicate
isCodexInteractiveResumeCommandnow classifies them, andcreateRuntimeRotationProxyContextIfEnabledroutes them tocreateRuntimeRotationAppHelperContextwith the same{ detachOnExit: true, useCanonicalHome: true }the bare TUI already uses.isCodexInteractiveTuiCommandkeeps its exact meaning, andshouldUseRuntimeRoutingForForwardedArgsis untouched —resume/forkstay inrequestCommands, so account rotation stays enabled. This is the transport change only; nothing is dropped by leaving the shadow path, sinceproxyAppServerAccountReadis only set forapp-server, which these are not.Shutdown (
scripts/codex.js).stopRuntimeRotationAppHelpersentSIGTERM, stopped waiting after the 2s graceful window, and returned with the helper and its piped stdio still referenced. Because the helper is spawnedstdio: ["ignore", "pipe", "pipe"], those pipes keep the wrapper's event loop alive on their own — the wrapper setsprocess.exitCodeand relies on the loop draining, so it never exited. Shutdown now escalates toSIGKILLpast the graceful window and then unconditionally destroys and unrefs the helper's streams. The stream teardown is the load-bearing part on Windows, where the signals are emulated as unconditional termination.waitForRuntimeRotationAppHelperExitalso removes its owncloselistener when the timeout wins, and an already-exited helper now short-circuits instead of waiting out the full window.Help short-circuit (
scripts/codex.js). Moving these commands to the interactive branch had a side effect: that branch passesdetachOnExit: true, and help always exits 0, soresume --helpwould spawn a detached helper and leave it idling for its full timeout. Before this PR the same invocation built a shadow home and a proxy, but both were torn down when the wrapper exited — so this was a new leak, not a pre-existing cost.shouldUseRuntimeRoutingForForwardedArgsnow short-circuits the whole transport for the help form of any request command, matching theapp --helpandapp-server --helpprecedent already in that function. Raised by Greptile in review, then extended fromapp/resume/forktoexec/reviewfor consistency: those never stranded a helper, but they did mirror an entire shadow Codex home just to print help. The rule is now one sentence — a request command that is only printing help makes no model requests, so it needs no transport — and it is keyed off the help flag rather than the command, so real runs are untouched.Docs.
docs/development/ARCHITECTURE.mdtransport table anddocs/development/CONFIG_FLOW.md§6 listresume/forkunder the canonical-home path, with the reasoning and the bounded-shutdown contract recorded.Validation
npm run lintnpm run typecheck(plusnpm run typecheck:scripts)npm testnpm test -- test/documentation.test.ts(32/32)npm run buildFifteen regression tests were added, and each was confirmed to fail against the unfixed wrapper rather than merely pass against the fixed one:
resumeRESUME_HOME_IS_ORIGINAL:false— took the shadow pathforkRESUME_HOME_IS_ORIGINAL:false— took the shadow pathexecstays on shadow homereviewstays on shadow homeSIGTERM(POSIX only)resume --help/resume -hstart no transportfork --help/fork -hstart no transportexec --help/exec -hstart no transportreview --help/review -hstart no transportresume <session-id>still starts the proxyFull suite, both platforms:
origin/maintree scores 5252 passed, the same 19 failed in that container, so those are pre-existing environment failures (platform-specific and root-user tests), not regressions. The delta is exactly +15 tests, all passing.Both shutdown tests bound their
spawnSynccall (runWrapper's opt-intimeoutMs) and assert the wrapper actually returned.spawnSyncblocks the worker thread, so Vitest'stestTimeoutcannot interrupt it — without the bound, a shutdown regression hangs the whole run instead of failing. Against the unfixed wrapper both now fail in ~12s naming the cause. Only these two tests opt in, so the rest of the file is unchanged.The
SIGTERM-escalation test is skipped onwin32:child.kill()there is always an unconditional terminate, so a helper cannot ignore it and the escalation is unreachable. The cross-platform stdio test covers the same failure on Windows. Both were verified on real Linux, not just reasoned about.Docs and Governance Checklist
docs/getting-started.mdupdated (if onboarding flow changed)docs/features.mdupdated (if capability surface changed)docs/reference/*pages updated — maintainer docsARCHITECTURE.mdandCONFIG_FLOW.mdupdated; no user-facing command, setting, or path changeddocs/upgrade.mdupdated (if migration behavior changed)SECURITY.mdandCONTRIBUTING.mdreviewed for alignmentNo CHANGELOG entry: this repo's changelog is written by
chore(release)commits, matching #639, the PR that introduced this canonical-home path.Risk and Rollback
resume/forknow read and write the realCODEX_HOMEin place rather than a per-session copy, so a crash mid-session can no longer be discarded with the shadow directory. That is how the stock CLI already behaves, and how the bare interactive TUI has behaved since fix(codex): avoid runtime shadow reindex on TUI startup #639.test/codex-bin-wrapper.test.tsalready proves two overlapping canonical sessions each keep their state. This PR widens that existing exposure to two more commands rather than introducing it.scripts/codex.js; no persisted state, config schema, or on-disk format changes, so no migration is involved.Additional Notes
The issue reporter diagnosed this correctly and their patch shape is what landed, with two deliberate differences. First, the routing uses a separate predicate rather than widening
isCodexInteractiveTuiCommand, so that function's name stays accurate and the new branch is independently testable. Second, the stream teardown is unconditional and an already-exited helper short-circuits the wait, which avoids charging a 2s delay on the common path.One thing worth flagging for reviewers: the original proposed patch guarded on
helper.killedand returned early, which would skip teardown entirely on a second stop call. The version here keys offexitCode/signalCodeinstead, so a helper that iskilledbut still alive continues to escalation.note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
the pr routes interactive
resumeandforkcommands through the canonical codex home and makes helper shutdown bounded, including windows stream cleanup. it also fixes the prior help-only helper leak without changing real request routing; concurrent canonical-home sessions remain explicitly supported.resumeandfork.Confidence Score: 5/5
the pr appears safe to merge.
the prior help-only helper leak is fixed by bypassing runtime transport for help forms, while real resume, fork, exec, and review invocations retain their intended routing; no blocking failure remains.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD args[forwarded codex arguments] --> help{help-only request command?} help -->|yes| direct[official cli without runtime transport] help -->|no| interactive{bare tui, resume, or fork?} interactive -->|yes| canonical[canonical home app helper] interactive -->|no| shadow[shadow home runtime proxy] canonical --> exit{clean cli exit?} exit -->|yes| detach[detach helper] exit -->|no| stop[sigterm, bounded wait, sigkill, release streams]Reviews (4): Last reviewed commit: "fix(codex): skip the rotation transport ..." | Re-trigger Greptile
Context used: