Skip to content

exec: add fork subcommand for non-interactive session forking #17568

Closed as duplicate

Description

@hampsterx

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 expose codex exec as an MCP server cannot offer forkFromSession parameters 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:

  1. Prompt match arm for ExecCommand::Fork (mirrors resume: resolves prompt, builds UserInput items, returns InitialOperation::UserTurn).

  2. Thread setup branch: resolves source thread ID (UUID parse or name lookup via state DB + thread/list), sends ClientRequest::ThreadFork, converts response to SessionConfiguredEvent. If source session not found, hard error (not a fallback to new session).

  3. Helper functions mirroring the existing resume/start pattern:

  • thread_fork_params_from_config: matches TUI's version (sets ephemeral, persist_extended_history: true, same field list).
  • session_configured_from_thread_fork_response: maps ThreadForkResponse through the shared session_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_prompt
  • fork_with_session_id_only
  • fork_with_images
  • fork_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: ForkArgs struct, Command::Fork variant.
  • codex-rs/exec/src/lib.rs: fork execution flow, helpers, forked_from_id plumbing 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 resume then 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-from flag to bare codex exec: conflates session creation modes. The subcommand pattern (resume, review, fork) is cleaner and matches how the TUI already organizes these operations.
  • Support --last for 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

  1. 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.
  2. 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: clean
  • cargo 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.

Activity

  1. added
    enhancementNew feature or request
    CLIIssues related to the Codex CLI
    execIssues related to the `codex exec` subcommand
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Apr 12, 2026
  2. removed
    CLIIssues related to the Codex CLI
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestexecIssues related to the `codex exec` subcommand

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions