exec: add fork subcommand for non-interactive session forking
What feature would you like to see?
Add a codex exec fork <session-id> [prompt] subcommand that creates a forked conversation from an existing session and runs the prompt on the new thread, mirroring the codex exec resume pattern. All server-side infrastructure (thread/fork RPC, ThreadForkParams/ThreadForkResponse types, Thread.forked_from_id metadata) already exists and is consumed by the TUI; only the codex exec CLI layer is missing.
Why
The TUI supports forking sessions via /fork and codex fork, but there is no non-interactive equivalent. External tools and automation that wrap codex exec --json cannot branch from an existing session programmatically:
- Agent frameworks that want to explore multiple solution paths from the same starting point cannot fork without spawning the full TUI.
- MCP bridge tools (like
codex-mcp-bridge) that exposecodex execas an MCP server cannot offerforkFromSessionparameters because the CLI has no fork subcommand. - CI/CD pipelines that want to re-run a previous session's context with a different prompt must either resume (mutating the original session) or start fresh (losing context).
The gap is purely at the CLI layer. The app-server already handles thread/fork (see app-server/README.md lines 260-268), the protocol types are stable and used by the TUI, and SessionConfiguredEvent already has a forked_from_id field.
Current state (verified against 3895ddd6)
codex-rs/tui/src/app_server_session.rs
fork_thread() at line 335: sends ClientRequest::ThreadFork, builds session state
thread_fork_params_from_config() at line 907: builds ThreadForkParams from Config
thread_session_state_from_thread_fork_response() at line 1024: maps response
codex-rs/exec/src/lib.rs
Command enum at line 118: Resume, Review — no Fork variant
Thread setup block at line 636: handles Resume, falls through to ThreadStart — no Fork branch
session_configured_from_thread_response() at line 1007: hardcodes forked_from_id: None
(pre-existing: discards Thread.forked_from_id from start/resume responses too)
The TUI path shows the complete fork wiring. codex exec handles resume through the same thread/list + thread/resume APIs. Fork follows the identical pattern but with ClientRequest::ThreadFork and a hard error on session-not-found (no fallback to new session).
Proposal
Add Fork(ForkArgs) to the Command enum and wire it into the execution flow. Three files, single crate (codex-exec):
cli.rs: New ForkArgs struct and Command::Fork variant.
Command::Fork(ForkArgs)
pub struct ForkArgs {
pub session_id: String, // required positional (UUID or thread name)
pub images: Vec<PathBuf>, // optional --image/-i
pub prompt: Option<String>, // optional positional (reads stdin if omitted, same as resume)
}
Simpler than ResumeArgs: no --last/--all (fork source is always explicit).
lib.rs: Three additions:
- Prompt match arm for
ExecCommand::Fork(mirrors resume: resolves prompt, buildsUserInputitems, returnsInitialOperation::UserTurn).
- Thread setup branch: resolves source thread ID (UUID parse or name lookup via state DB +
thread/list), sendsClientRequest::ThreadFork, converts response toSessionConfiguredEvent. If source session not found, hard error (not a fallback to new session).
- Helper functions mirroring the existing resume/start pattern:
thread_fork_params_from_config: matches TUI's version (setsephemeral,persist_extended_history: true, same field list).session_configured_from_thread_fork_response: mapsThreadForkResponsethrough the sharedsession_configured_from_thread_response.resolve_fork_source_thread_id: UUID parse or name lookup, filters by cwd.
Bonus fix: session_configured_from_thread_response currently hardcodes forked_from_id: None. The patch adds a forked_from_id parameter and updates all call sites (start, resume, fork) to pass response.thread.forked_from_id.clone(). This matches how the TUI already handles it (app_server_session.rs lines 984-1044) and fixes --json mode silently discarding fork metadata from start/resume responses.
cli_tests.rs: Four parsing tests following the existing resume pattern:
fork_with_session_id_and_promptfork_with_session_id_onlyfork_with_imagesfork_requires_session_id(clap rejects missing positional)
Scope
Three files, +241 LOC, -3 LOC (the 3 deletions are the hardcoded None in existing call sites), within codex-exec only:
codex-rs/exec/src/cli.rs:ForkArgsstruct,Command::Forkvariant.codex-rs/exec/src/lib.rs: fork execution flow, helpers,forked_from_idplumbing fix.codex-rs/exec/src/cli_tests.rs: four CLI parsing tests.
No changes to codex-core, codex-protocol, codex-app-server, or codex-app-server-protocol. No new crate dependencies.
Backwards compatibility
Purely additive. The fork subcommand is a new Command variant. Existing resume, review, and bare codex exec behavior is unchanged. The forked_from_id fix is also backwards-compatible: it populates a field that was previously always None, so --json consumers that ignored it continue to work and those that wanted it now get it.
Alternatives considered
- Use
resumethen manually reset context: mutates the original session. Fork creates a clean branch point without side effects on the source thread. - Spawn TUI with
codex fork <id>and extract output: requires terminal allocation, defeats the purpose of non-interactive execution, brittle to parse. - Add
--fork-fromflag to barecodex exec: conflates session creation modes. The subcommand pattern (resume,review,fork) is cleaner and matches how the TUI already organizes these operations. - Support
--lastfor fork: deferred for v1. Fork source should be explicit to avoid accidentally branching the wrong session. Can be added later if there's demand.
Open questions
- Should fork support
--all(search globally, not just current cwd) for name-based resolution? Currently scoped to cwd for safety, matching resume's default behavior. - Should a promptless fork be allowed (just create the forked thread and exit without starting a turn)? Current behavior matches resume: reads from stdin if no prompt argument, errors if stdin is empty. A no-op fork could be useful for pre-creating branches.
Implementation
I have a working implementation on a feature branch against 3895ddd6. Happy to open a PR if this is directionally acceptable.
Verification results:
cargo check -p codex-exec: cleancargo test -p codex-exec: 61 tests passing (including 4 new CLI parsing tests)cargo clippy -p codex-exec: clean
Context
Driven by codex-mcp-bridge, which wraps codex exec as an MCP server. Adding a forkFromSession parameter to its codex_exec tool depends on codex exec fork existing. Without it, the bridge cannot offer session branching, which is valuable for multi-path exploration and A/B comparison workflows in agent frameworks that sit on top of MCP.