Direct Codex restore ignores inherited Subrouter routing
Summary
Part of #21261.
When a terminal restores a Codex session from a direct (not Subrouter-routed) launch record, cmux restore builds the argv and execs it without a shell. The environment it hands to Codex starts from the restoring shell's own environment. For a proven routed record, the planner already drops inherited SUBROUTER_CODEX_* and CMUX_CUSTOM_CODEX_PATH and refills them from the record. For a direct record, it kept whatever the shell had inherited. That has two effects:
- The managed Codex wrapper reads
CMUX_CUSTOM_CODEX_PATHbefore it searches PATH. An inherited value (for example from a shell started undersr) can make it exec that launcher instead of the saved Codex command. - The wrapper re-exports the Subrouter resume marker when it is present and argv contains
model_provider=subrouter. The resumed process is then captured as routed, so the next restore runssr codex resume.
Now the launch record alone decides routing. For every non-direct Codex restore, the planner drops those restore-owned keys from the inherited environment before merging the record's environment. Routed records still get their proven values back, because that merge comes afterwards. Direct-mode replays (.direct) are unchanged.
What #21261 describes, a bundle path with spaces treated as an unquoted PATH fragment, does not reproduce on current main:
- Local restore types the bare words
cmux restore codex <id>. - The bundle
Resources/bindirectory reaches PATH as an environment value, not shell text (TerminalSurface+RuntimeSurfaceCreation.swift). resolveRestoreExecutablesplits PATH only on:.- Every shell-string fallback I traced single-quotes its words.
The new regression puts the bundle path with spaces and a shim path with spaces into the same restore. It pins that PATH survives unchanged.
Testing
- Added
AgentRestoreLaunchTests.directCodexRestoreIgnoresInheritedSubrouterRoutingFromSpacedBundlePath. It is a direct Codex record (codex -c model_provider=subrouter, no routing proof) restored from a shell that inherited the Subrouter variables, with bundle and shim paths containing spaces. It asserts that argv starts with the wrapper shim plusresume <checkpoint>, the provider flag and checkpoint are kept, there is nosr, PATH is unchanged, and the inherited routing keys are gone. - The test is committed before the fix (b4191385f9fda, fix in 20bc30bbfa92d). I could not record red/green locally: the Swift build host (
cmux-lawrence-2) does not resolve from this machine (HQ REPAIR row fornx-remoteDNS). TheCMUXAgentLaunchpackage tests in hosted CI are the executed evidence for this PR. python3 scripts/verify-local.pypassed: Swift syntax, localization defaults, package groups, feature flags.git diff --checkis clean. No native compilation ran locally.
Changelog
Fixed: Restoring a Codex session that was started directly no longer picks up a Subrouter launcher inherited from the terminal's environment
Proof
No UI change. The proof is the CMUXAgentLaunch package tests in CI.
Checklist
- Behavior changes have added or updated tests, or Testing says why not
- Reviewed with a subagent before merge (cmux-review), and all bot and human review comments resolved
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Stops direct Codex restores from inheriting Subrouter routing from the restoring shell. Previously, a direct launch record kept inherited SUBROUTER_CODEX_* and CMUX_CUSTOM_CODEX_PATH values, so the managed wrapper could exec a Subrouter launcher instead of the saved Codex command and the resumed process could be re-captured as routed. Now the launch record alone decides routing: for every non-direct Codex restore, the planner drops those keys from the inherited environment before merging the record's environment. PATH values pass through unchanged, including paths with spaces.
Testing
- Adds
directCodexRestoreIgnoresInheritedSubrouterRoutingFromSpacedBundlePath, which restores a direct Codex record from a shell with inherited Subrouter variables and spaces in the bundle and shim paths, asserting arg routing, PATH, and environment cleanup.