Commit Graph

3134 Commits

  • 2- Use string service tiers in session protocol (#20971)
    ## Summary
    - break service tier session/op/app-server protocol fields from the
    closed enum to string tier ids
    - send the service tier string directly through model requests, prewarm,
    compaction, memories, and TUI/app-server turn starts
    - regenerate app-server protocol JSON/TypeScript schemas, removing the
    standalone ServiceTier TS enum
    
    ## Verification
    - just fmt
    - cargo check -p codex-core -p codex-app-server -p codex-tui
    - just write-app-server-schema
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Move installation ID resolution out of core startup (#21182)
    ## Summary
    
    - resolve or inject the installation ID before core startup and pass it
    through `ThreadManager`, `CodexSpawnArgs`, and `Session` as a plain
    `String`
    - keep child sessions on the parent installation ID instead of
    rediscovering it inside core
    - propagate installation ID startup failures in `mcp-server` instead of
    panicking
    
    ## Why
    
    Core was still touching the filesystem on the session startup path to
    discover `installation_id`. This moves that work to the outer host
    boundary so core no longer depends on `codex_home` reads during session
    construction.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Propagate cache key and service tiers in compact (#21249)
    ## Why
    
    `/responses/compact` should preserve the request-affinity fields that
    apply to the active auth mode. ChatGPT-auth compact requests need the
    effective `service_tier`, and compact requests for every auth mode need
    the stable `prompt_cache_key`, so compaction does not quietly lose
    routing or cache behavior that normal sampling already has.
    
    This follows the request-parity direction from #20719, but keeps the net
    change focused on the compact payload fields needed here.
    
    ## What changed
    
    - Add `service_tier` and `prompt_cache_key` to the compact endpoint
    input payload.
    - Build the remote compact payload from the existing responses request
    builder output so `Fast` still maps to `priority` when compact sends a
    service tier.
    - Pass the turn service tier into remote compaction, but only include it
    in compact payloads for ChatGPT-backed auth.
    - Keep `prompt_cache_key` on compact payloads for all auth modes.
    - Add request-body diff snapshot coverage in
    `core/tests/suite/compact_remote.rs` for:
    - API-key auth reusing `prompt_cache_key` while omitting `service_tier`
    even when `Fast` is configured.
      - ChatGPT auth reusing both `service_tier` and `prompt_cache_key`.
    - Drive the snapshot coverage through five varied turns: plain text,
    multi-part text, tool-call continuation, image+text input, local-shell
    continuation, and final-turn reasoning output.
    
    ## Verification
    
    - Added insta snapshots for compact request-body parity against the last
    normal `/responses` request after five varied turns.
    - Not run locally per repo guidance; relying on GitHub CI for test
    execution.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: include thread ID in MCP turn metadata (#21329)
    ## Why
    
    MCP tool calls already include `session_id` in `x-codex-turn-metadata`,
    but descendant threads intentionally share that value with the root
    thread. Consumers that need to correlate work at the concrete thread
    level also need the current `thread_id`.
    
    ## What changed
    
    - add `thread_id` to `x-codex-turn-metadata` while preserving
    `session_id` as the shared session identity
    - thread the two identities separately through normal turns and spawned
    review threads
    - add regression coverage for resumed sessions, reserved metadata
    fields, and deferred MCP tool calls
    
    ## Verification
    
    - added focused coverage in `core/src/session/tests.rs`,
    `core/src/turn_metadata_tests.rs`, and `core/tests/suite/search_tool.rs`
  • feat: add session_id (#20437)
    ## Summary
    
    Related to
    https://openai.slack.com/archives/C095U48JNL9/p1777537279707449
    TLDR:
    We update the meaning of session ids and thread ids:
    * thread_id stays as now
    * session_id become a shared id between every thread under a /root
    thread (i.e. every sub-agent share the same session id)
    
    This PR introduces an explicit `SessionId` and threads it through the
    protocol/client boundary so `session_id` and `thread_id` can diverge
    when they need to, while preserving compatibility for older serialized
    `session_configured` events.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Support Codex Apps auth elicitations (#19193)
    ## Summary
    
    - request URL-mode MCP elicitations when Codex Apps tool calls fail with
    connector auth metadata
    - route Codex Apps auth URL elicitations into the TUI app-link flow
    
    ## Test plan
    
    - `just fmt`
    - `cargo test -p codex-core mcp_tool_call::tests`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-tui bottom_pane::app_link_view::tests`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp`
    - `just fix -p codex-tui`
    
    Also attempted broader local runs:
    
    - `cargo test -p codex-core` fails in unrelated
    config/request-permission/proxy-sensitive tests under the current Codex
    Desktop environment.
    - `cargo test -p codex-tui` fails in unrelated status
    snapshots/trust-default tests because the ambient environment renders
    workspace-write/network permission defaults.
  • [mcp] Return Accept early per feedback. (#21277)
    - [x] Return Accept early when auto_deny is enabled per feedback.
  • Preserve session MCP config on refresh (#21055)
    # Overview
    MCP refreshes were rebuilding active threads from fresh disk-backed
    config only, which dropped thread-start session overlays such as
    app-injected MCP servers. This keeps refreshes current with disk config
    while preserving the thread-local config that only the active thread
    knows about.
    
    # Changes
    - Rebuild refreshed config per active thread using that thread's current
    `cwd`, rather than fanning out one app-server config to every thread.
    - Preserve each thread's `SessionFlags` layer while replacing reloadable
    config layers with freshly loaded config, then derive the MCP refresh
    payload from the rebuilt result.
    - Move MCP refresh orchestration into app-server so manual refreshes
    fail loudly while background refreshes remain best-effort, and route
    plugin-triggered refreshes through the same per-thread reload path.
    - Add regression coverage for session overlays, fresh project config,
    plugin-derived MCP config, current requirements, and strict vs
    best-effort refresh behavior.
    
    # Verification
    - Passed focused Rust coverage for the thread-config rebuild behavior
    and deferred MCP refresh flow, plus `cargo test -p codex-app-server
    --lib`.
    - Verified end to end in the Codex dev app against the locally built
    CLI: registered an MCP via thread config, verified that it could be used
    successfully before refresh, manually triggered MCP refresh, and
    verified that it continued to be available afterward.
  • [codex-analytics] rework thread_source for thread analytics (#20949)
    ## Summary
    - make `thread_source` an explicit optional thread-level field on
    `thread/start`, `thread/fork`, and returned thread payloads
    - persist `thread_source` in rollout/session metadata so resumed live
    threads retain the original value
    - replace the old best-effort `session_source` -> `thread_source`
    mapping with an explicit caller-supplied analytics classification
    
    ## Why
    Before this change, analytics `thread_source` was populated by a
    best-effort mapping from `session_source`. `session_source` describes
    the runtime/client surface, not the actual thread-level origin, so that
    projection was not accurate enough to distinguish cases such as `user`,
    `subagent`, `memory_consolidation`, and future thread origins reliably.
    
    Making `thread_source` explicit keeps one thread-level analytics field
    while letting callers provide the real classification directly instead
    of recovering it indirectly from `session_source`.
    
    ## Impact
    For new analytics events, `thread_source` now reflects the explicit
    thread-level classification supplied by the caller rather than an
    inferred value derived from `session_source`. Existing protocol fields
    remain optional; callers that omit `threadSource` now produce `null`
    instead of a best-effort inferred value.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo test -p codex-analytics -p codex-core -p
    codex-app-server-protocol --no-run`
    - `cargo test -p codex-app-server-protocol
    generated_ts_optional_nullable_fields_only_in_params`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `cargo test -p codex-core
    resume_stopped_thread_from_rollout_preserves_thread_source`
  • [codex] Remove legacy ListSkills op (#21282)
    ## Why
    
    `skills/list` is already exposed through app-server v2 and covered by
    the app-server test suite. Keeping the separate core `Op::ListSkills`
    path leaves a duplicate legacy protocol surface that no longer needs to
    be maintained.
    
    ## What Changed
    
    - Removed `Op::ListSkills` and `EventMsg::ListSkillsResponse` from the
    core protocol.
    - Deleted the corresponding core session handler and stale core
    integration tests.
    - Removed rollout/MCP ignore branches and protocol v1 docs references
    for the deleted event/op.
    - Left app-server `skills/list` and its existing coverage intact.
    
    ## Validation
    
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core --test all suite::skills`
    - `cargo check -p codex-mcp-server -p codex-rollout -p
    codex-rollout-trace`
    - `just fix -p codex-core`
  • Add model and reasoning effort to MCP turn metadata (#21219)
    ## Why
    - Similar change as https://github.com/openai/codex/pull/19473.
    - Without change: MCP tool calls receive
    `_meta["x-codex-turn-metadata"]` with `session_id`, `turn_id`, and
    `turn_started_at_unix_ms`.
    - Issue: MCP servers may want the model and reasoning effort to better
    understand tool-call behavior and latency relative to turn start.
    
    ## What Changed
    - With change: MCP turn metadata now includes `model` and
    `reasoning_effort`, propagated in `_meta["x-codex-turn-metadata"]`.
    - Normal `/responses` turn metadata headers are unchanged.
    
    ## Verification
    - `codex-rs/core/src/mcp_tool_call_tests.rs`
    - `codex-rs/core/src/turn_metadata_tests.rs`
    - `codex-rs/core/tests/suite/search_tool.rs`
  • [codex] Move thread naming to app server (#21260)
    ## Why
    
    Thread names are app-server metadata now, backed by the thread store and
    sqlite state database. Keeping a core `SetThreadName` op plus a rollout
    `thread_name_updated` event made rename persistence live in the wrong
    layer and required historical replay support for an event that new
    app-server flows should not write.
    
    ## What changed
    
    - Removed `Op::SetThreadName` and `EventMsg::ThreadNameUpdated` from the
    core protocol and deleted the core handler path that appended rename
    events to rollouts.
    - Updated app-server `thread/name/set` so both loaded and unloaded
    threads write through thread-store metadata and app-server emits
    `thread/name/updated` notifications.
    - Updated local thread-store name metadata updates to write sqlite title
    metadata and the legacy thread-name index without appending rollout
    events.
    - Removed state extraction and rollout handling for the deleted
    thread-name event.
    
    ## Validation
    
    - `cargo test -p codex-app-server thread_name_updated_broadcasts`
    - `cargo test -p codex-app-server
    thread_name_set_is_reflected_in_read_list_and_resume`
    - `cargo test -p codex-thread-store
    update_thread_metadata_sets_name_on_active_rollout_and_indexes_name`
    - `cargo test -p codex-state`
    - `cargo check -p codex-mcp-server -p codex-rollout-trace`
    - `just fix -p codex-app-server -p codex-thread-store -p codex-state -p
    codex-mcp-server -p codex-rollout-trace`
    
    ## Docs
    
    No external documentation update is expected for this internal ownership
    change.
  • linux-sandbox: use standalone bundled bwrap (#21255)
    **Summary**
    - Add `codex-bwrap`, a standalone `bwrap` binary built from the existing
    vendored bubblewrap sources.
    - Remove the linked vendored bwrap path from `codex-linux-sandbox`;
    runtime now prefers system `bwrap` and falls back to bundled
    `codex-resources/bwrap`.
    - Add bundled SHA-256 verification with missing/all-zero digest as the
    dev-mode skip value, then exec the verified file through
    `/proc/self/fd`.
    - Keep `launcher.rs` focused on choosing and dispatching the preferred
    launcher. Bundled lookup, digest verification, and bundled exec now live
    in `linux-sandbox/src/bundled_bwrap.rs`; Bazel runfiles lookup lives in
    `linux-sandbox/src/bazel_bwrap.rs`; shared argv/fd exec helpers live in
    `linux-sandbox/src/exec_util.rs`.
    - Teach Bazel tests to surface the Bazel-built `//codex-rs/bwrap:bwrap`
    through `CARGO_BIN_EXE_bwrap`; `codex-linux-sandbox` only honors that
    fallback in debug Bazel runfiles environments so release/user runtime
    lookup stays tied to `codex-resources/bwrap`.
    - Allow `codex-exec-server` filesystem helpers to preserve just the
    Bazel bwrap/runfiles variables they need in debug Bazel builds, since
    those helpers intentionally rebuild a small environment before spawning
    `codex-linux-sandbox`.
    - Verify the Bazel bwrap target in Linux release CI with a build-only
    check. Running `bwrap --version` is too strong for GitHub runners
    because bubblewrap still attempts namespace setup there.
    
    **Verification**
    - Latest update: `cargo test -p codex-linux-sandbox`
    - Latest update: `just fix -p codex-linux-sandbox`
    - `cargo check --target x86_64-unknown-linux-gnu -p codex-linux-sandbox`
    could not run locally because this macOS machine does not have
    `x86_64-linux-gnu-gcc`; GitHub Linux Bazel CI is expected to cover the
    Linux-only modules.
    - Earlier in this PR: `cargo test -p codex-bwrap`
    - Earlier in this PR: `cargo test -p codex-exec-server`
    - Earlier in this PR: `cargo check --release -p codex-exec-server`
    - Earlier in this PR: `just fix -p codex-linux-sandbox -p
    codex-exec-server`
    - Earlier in this PR: `bazel test --nobuild
    //codex-rs/linux-sandbox:linux-sandbox-all-test
    //codex-rs/core:core-all-test
    //codex-rs/exec-server:exec-server-file_system-test
    //codex-rs/app-server:app-server-all-test` (analysis completed; Bazel
    then refuses to run tests under `--nobuild`)
    - Earlier in this PR: `bazel build --nobuild //codex-rs/bwrap:bwrap`
    - Prior to this update: `just bazel-lock-update`, `just
    bazel-lock-check`, and YAML parse check for
    `.github/workflows/bazel.yml`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21255).
    * #21257
    * #21256
    * __->__ #21255
  • codex: use ThreadStore history for core review forks (#20577)
    - fork loaded parent threads from `ThreadStore` history in core agent
    control paths
    - migrate guardian review fork history to loaded session history instead
    of rereading rollout files
    
    ## Verification
    - `cargo test -p codex-core spawn_agent_fork`
  • Inject state DB, agent graph store (#20689)
    ## Why
    
    We want the agent graph store to be passed down the stack as a real
    dependency, the same way we already treat the thread store.
    
    This will let us inject the agent graph store as a real dependency and
    support implementations other than the local SQLite-backed one. Right
    now most code instantiates a state DB and an agent graph store
    just-in-time. Ideally, we would not depend on the state DB directly but
    only read through the higher-level interfaces.
    
    This change makes the dependency boundaries explicit and moves state DB
    initialization to process bootstrap instead of hiding it inside local
    store implementations.
    
    ## What changed
    
    - `ThreadManager` now requires a `StateDbHandle` and an
    `AgentGraphStore` at construction time instead of treating them as
    optional internals.
    - The local store constructors no longer lazily initialize SQLite.
    Callers now initialize the state DB once per process and use that shared
    handle to build:
      - `LocalThreadStore`
      - `LocalAgentGraphStore`
    - App bootstraps (`app-server`, `mcp-server`, `prompt_debug`, and the
    thread-manager sample) now initialize the state DB up front and inject
    the resulting handle down the stack.
    - `app-server` now consistently uses its process-scoped state DB handle
    instead of reopening SQLite or trying to recover it from loaded threads.
    - Device-key storage now reuses the shared state DB handle instead of
    maintaining its own lazy opener.
    - The thread archive / descendant traversal paths now use the injected
    `AgentGraphStore` instead of reaching through local
    thread-store-specific state.
    
    ## Verification
    
    - `cargo check -p codex-core -p codex-thread-store -p codex-app-server
    -p codex-mcp-server -p codex-thread-manager-sample --tests`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-core
    thread_manager_accepts_separate_agent_graph_store_and_thread_store --
    --nocapture`
    - `cargo test -p codex-app-server
    thread_archive_archives_spawned_descendants -- --nocapture`
  • Auto-deny MCP elicitations for Xcode 26.4 clients (#21113)
    ## Summary
    
    Xcode 26.4 was built against app-server behavior from before MCP
    elicitation requests became client-visible in CLI 0.120.0 via #17043.
    That client line does not expect the new events/messages, so this PR
    restores the old behavior for exactly that client/version combination.
    
    The compatibility handling stays in the app-server layer: when the
    initialized client is `Xcode` and its version starts with `26.4`, the
    app server marks the live Codex thread so MCP elicitations are
    auto-denied. The flag is applied on thread start/resume/fork/turn
    attachment, carried through `Codex`/`CodexThread`, and stored on
    `McpConnectionManager` so refreshed MCP managers preserve the behavior.
    
    ## Notes
    
    This is intentionally narrow and includes a TODO to remove the
    compatibility path once Xcode 26.4 ages out.
  • [codex] Split tool handlers by tool name (#20687)
    ## Why
    
    Tool registration used to bind a tool name to a handler externally,
    which left ownership split between the registry plan and the handler
    implementation. Some built-in handlers also multiplexed multiple in-core
    tools by switching on the invoked tool name internally.
    
    This moves the registry identity onto the handler itself and makes
    built-in multi-tool areas use separate concrete handlers, so each
    registered handler instance owns exactly one tool name and one dispatch
    path.
    
    ## What Changed
    
    - Added `ToolHandler::tool_name()` and changed
    `ToolRegistryBuilder::register_handler` to derive the registry key from
    the handler.
    - Split built-in multiplexed handlers into concrete per-tool handlers
    for unified exec, shell/local shell/container exec, MCP resources, goal
    tools, and agent job tools.
    - Kept name-carrying handler instances only where the runtime target is
    inherently external or dynamic, such as MCP tools, dynamic tools, and
    unavailable placeholders.
    - Updated `ToolHandlerKind` and registry-plan construction so plan
    entries map directly to concrete handler registrations.
    
    ## Verification
    
    - `cargo test -p codex-tools tool_registry_plan`
    - `cargo test -p codex-core --lib tools::registry_tests`
    - `just fix -p codex-tools`
    - `just fix -p codex-core`
  • feat(tui): redesign session picker (#20065)
    ## Why
    
    The resume/fork picker is becoming the main way users recover previous
    work, but the old fixed table made sessions hard to scan once thread
    names, branches, working directories, and timestamps all mattered. This
    redesign makes the picker denser by default, easier to search, and safer
    to inspect before resuming or forking.
    
    <table>
    <tr>
    <td>
    <img width="1660" height="1103" alt="CleanShot 2026-05-03 at 12 34 10"
    src="https://github.com/user-attachments/assets/313ede1d-1da4-4863-acd2-56b3e27e9703"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 34 15"
    src="https://github.com/user-attachments/assets/cfde7d5c-bab0-4994-a807-254e53f344ea"
    />
    </td>
    </tr>
    <tr>
    <td>
    <img width="1664" height="1107" alt="CleanShot 2026-05-03 at 12 39 22"
    src="https://github.com/user-attachments/assets/e1ee58ca-4dc5-4a35-ae0f-47562da3974c"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 35 09"
    src="https://github.com/user-attachments/assets/9c888072-eedf-4f45-985c-0c14df28bcc7"
    />
    </td>
    </tr>
    </table>
    
    ## What Changed
    
    - Replaces the old session table with responsive session rows that
    prioritize the session name or preview, then show timestamp, cwd, and
    branch metadata.
    - Makes dense view the default while keeping comfortable view available
    through `Ctrl+O`.
    - Persists the picker view preference in `[tui].session_picker_view`,
    including active profile-scoped config.
    - Adds sort/filter controls for updated time, created time, cwd, and all
    sessions.
    - Expands search matching across session name, preview, thread id,
    branch, and cwd.
    - Makes `Esc` safer in search mode: it clears an active query before
    starting a new session.
    - Adds lazy transcript inspection:
      - `Space` expands recent transcript context inline.
      - `Ctrl+T` opens a transcript overlay.
      - raw reasoning visibility follows `show_raw_agent_reasoning`.
    - Keeps remote cwd filtering server-side for remote app-server sessions
    so local path normalization does not incorrectly hide remote results.
    - Updates snapshots and config schema for the new picker states and
    config option.
    
    ## How to Test
    
    1. Start Codex in a repo with several saved sessions.
    2. Press `Ctrl+R` / resume picker entry point.
    3. Confirm the picker opens in dense mode and shows session name or
    preview, timestamp, cwd, and branch metadata.
    4. Press `Ctrl+O` and confirm it switches between dense and comfortable
    views.
    5. Restart Codex and confirm the selected view persists.
    6. Type a query that matches a branch, cwd, thread id, or session name;
    confirm matching sessions appear.
    7. Press `Esc` while the query is non-empty and confirm it clears search
    instead of starting a new session.
    8. Select a session and press `Space`; confirm recent transcript context
    expands inline.
    9. Press `Ctrl+T`; confirm the transcript overlay opens and respects
    raw-reasoning visibility settings.
    
    Targeted tests:
    - `cargo test -p codex-tui resume_picker --no-fail-fast`
    - `cargo test -p codex-core
    runtime_config_resolves_session_picker_view_default_and_override`
    - `cargo test -p codex-core profile_tui_rejects_unsupported_settings`
    - `cargo check -p codex-thread-manager-sample`
    - `cargo insta pending-snapshots`
  • hook trust metadata and enforcement (#20321)
    # Why
    
    We want shared hook trust that both the app and the TUI can build on,
    but the metadata is only useful if runtime behavior agrees with it. This
    PR adds a single backend trust model for hooks so unmanaged hooks cannot
    run until the current definition has been reviewed, while managed hooks
    remain runnable and non-configurable.
    
    # What
    
    - persist `trusted_hash` alongside hook state in `config.toml`
    - expose `currentHash` and derived `trustStatus` through `hooks/list`
    - derive trust from normalized hook definitions so equivalent hooks from
    `config.toml` and `hooks.json` share the same trust identity
    - gate unmanaged hooks on trust before they enter the runnable handler
    set
    
    # Reviewer Notes
    
    - key file to review is `codex-rs/hooks/src/engine/discovery.rs`
    - the only **core** change is schema related
  • Route process tools to selected environments (#20647)
    ## Why
    When a turn exposes multiple selected environments, shell-style tools
    need a model-facing way to identify the intended target environment and
    handlers need to resolve that target before parsing cwd-relative
    permission fields or launching processes.
    
    This PR scopes that rollout to process tools. Filesystem-oriented tools
    such as `apply_patch`, `view_image`, and `list_dir` are intentionally
    left for follow-up slices.
    
    ## What Changed
    - Adds an `include_environment_id` option to shell-style tool schema
    builders.
    - Exposes optional `environment_id` on `shell`, `shell_command`, and
    `exec_command` only when `ToolEnvironmentMode::Multiple` is active.
    - Adds a shared handler helper that parses `environment_id` and
    `workdir` from JSON function-call arguments and returns the selected
    `Environment` plus effective absolute cwd.
    - Uses that helper in `shell`, `shell_command`, and `exec_command`
    handling so process execution uses the selected environment filesystem
    and cwd.
    - Changes `ExecCommandRequest` to carry a required resolved `cwd`,
    removing the process-manager fallback to the primary turn cwd for new
    exec commands.
    - Leaves `write_stdin` unchanged because it targets an existing process
    id, not a new environment.
    
    ## Testing
    - Added unit coverage for process-tool schema exposure, selected
    environment resolution, primary fallback, no-environment handling,
    unknown environment ids, and resolving cwd-relative permission paths
    against the selected environment cwd.
    - Added a remote-suite e2e coverage case for `exec_command` routing
    across explicit zero environments, one local environment, and
    local+remote environments.
    - Ran `just fmt` and `git diff --check`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(tui): add raw scrollback mode (#20819)
    ## Why
    
    Granular copy is particularly difficult with the current output. Part of
    it was solved with the introduction of the `/copy` command but when you
    only need to copy parts of a response, you still encounter some issues:
    
    - When you copy a paragraph, the result is a sequence of separate lines
    instead of one correctly joined paragraph.
    - When a word wraps, part of it stays on the original line and the rest
    appears at the start of the next line.
    - When you copy a long command, extra line breaks are often inserted,
    and command arguments can be split across multiple lines.
    
    
    https://github.com/user-attachments/assets/0ef85c84-9363-4aad-b43a-15fce062a443
    
    ## Solution
    
    Now that we own the scrollback and we re-create it when we resize, we
    have the opportunity of toggling between the raw text and the rich text
    we see today.
    
    - Add TUI raw scrollback mode with `tui.raw_output_mode`, `/raw
    [on|off]`, and the configurable `tui.keymap.global.toggle_raw_output`
    action.
    - Render transcript cells through rich/raw-aware paths so raw mode
    preserves source text and lets the terminal soft-wrap selection-friendly
    output.
    - Bind raw-mode toggle to `alt-r` by default, with the keybinding path
    toggling silently while `/raw` continues to emit confirmation messages.
    
    ## Related Issues
    
    Likely addressed by raw mode:
    
    - #12200: clean copy for multiline and soft-wrapped output. Raw mode
    removes Codex-inserted wrapping/indentation and lets the terminal
    soft-wrap logical lines.
    - #9252: command suggestions gain unwanted leading spaces when copied.
    Raw mode renders transcript text without the rich-mode left
    padding/gutter.
    - #8258: prompt output is hard to copy because of leading indentation.
    Raw mode renders user/source-backed transcript text without that
    decorative indentation.
    
    Partially or conditionally addressed:
    
    - #2880: copy/export message as Markdown. Raw mode exposes raw Markdown
    for terminal selection, but this PR does not add a dedicated
    export/copy-message command.
    - #19820: mouse drag selection + copy in the TUI. Raw mode improves
    terminal-native selection of output/history text, but this PR does not
    implement in-TUI mouse selection, highlighting, auto-copy, or composer
    selection.
    - #18979: copied content is divided into two parts. This should improve
    cases caused by Codex-inserted wraps/padding in rendered output; if the
    report is about pasting into the composer/input path, that remains
    outside this PR.
    
    ## Validation
    
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-config`
    - `cargo test -p codex-tui`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo test -p codex-tui
    raw_output_mode_can_change_without_inserting_notice -- --nocapture`
    - `cargo test -p codex-tui
    raw_slash_command_toggles_and_accepts_on_off_args -- --nocapture`
    - `cargo test -p codex-tui raw_output_toggle -- --nocapture`
    - `git diff --check`
    - `cargo insta pending-snapshots`
  • revert legacy notify deprecation (#21152)
    # Why
    
    Revert #20524 for now because the computer use plugin has not migrated
    off legacy `notify` yet. Keeping the deprecation in place today would
    show users a warning before the plugin path is ready to move, so this
    rolls the change back until that migration is complete.
    
    # What
    
    - revert the legacy `notify` deprecation change from #20524
    - restore the prior `notify` behavior and remove the temporary
    deprecation metrics/docs from that change
    
    Once the computer use plugin has migrated, we can land the same
    deprecation again.
  • Support PreToolUse additionalContext (#20692)
    # Why
    
    `PreToolUse` already exposes `hookSpecificOutput.additionalContext` in
    the generated hook schema, but the runtime still rejected it as
    unsupported. That leaves `PreToolUse` out of step with the other
    context-injecting hooks and prevents hook authors from attaching
    model-visible guidance to a pending tool call before it runs.
    
    # What
    
    - Parse `PreToolUse.additionalContext` and carry it through the hook
    event pipeline.
    - Record `PreToolUse` context at the hook boundary so successful context
    is preserved for both allowed and blocked calls without widening the
    tool registry surface.
    - Preserve existing deny behavior when context is combined with either
    `permissionDecision: "deny"` or the legacy `decision: "block"` shape.
  • Add goal lifecycle metrics (#20799)
    ## Why
    
    Adding goal metrics makes it possible to track how often goals are
    created, completed, and stopped by budget limits, plus the final token
    and wall-clock usage for terminal outcomes.
    
    ## What Changed
    
    - Added OpenTelemetry metric constants for goal lifecycle tracking:
    - `codex.goal.created`: increments each time a new persisted goal is
    created or an existing goal is replaced with a new objective.
    - `codex.goal.completed`: increments when a goal transitions to
    `complete`.
    - `codex.goal.budget_limited`: increments when a goal transitions to
    `budget_limited` because its token budget has been reached.
    - `codex.goal.token_count`: records the final persisted token count when
    a goal transitions to `complete` or `budget_limited`.
    - `codex.goal.duration_s`: records the final persisted elapsed
    wall-clock time, in seconds, when a goal transitions to `complete` or
    `budget_limited`.
    - Emitted creation metrics when a goal is created or replaced.
    - Emitted terminal outcome counters and final usage histograms when a
    goal transitions to `complete` or `budget_limited`, avoiding
    double-counting later in-flight accounting for already budget-limited
    goals.
    - Added focused `codex-core` tests for create/complete metrics and
    one-time budget-limit metrics.
  • tools: remove unused experimental list_dir tool (#21170)
    ## Why
    `list_dir` still carries a full spec/handler/test path, but nothing in
    the current model catalog advertises it via
    `experimental_supported_tools`. That leaves us maintaining an
    environment-backed tool surface that is effectively unused.
    
    ## What changed
    - delete the `list_dir` handler and its tests from `codex-core`
    - remove the `list_dir` spec builder, handler kind, and registry wiring
    from `codex-tools`
    - clean up the remaining internal README and registry tests so they no
    longer mention the removed tool
  • 1- Add model service tiers metadata (#20969)
    ## Why
    
    The model list needs to carry display-ready service tier metadata so
    clients can render tier choices with stable IDs, names, and
    descriptions. A raw speed-tier string list is not enough for richer UI
    copy or future tier labels.
    
    ## What changed
    
    - Added `ModelServiceTier` to shared model metadata with string `id`,
    `name`, and `description` fields.
    - Added `service_tiers` to `ModelInfo` and `ModelPreset`, preserving
    empty defaults for older cached model payloads.
    - Exposed `serviceTiers` on app-server v2 `Model` responses and threaded
    it through TUI app-server model conversion.
    - Marked legacy `additional_speed_tiers` / `additionalSpeedTiers`
    metadata as deprecated in source and generated schema output.
    - Regenerated app-server protocol JSON schema and TypeScript fixtures,
    including `ModelServiceTier.ts`.
    
    ## Verification
    
    - Ran `just write-app-server-schema`.
    - Did not run local tests per repo instruction; relying on PR CI.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Spill large hook outputs from context (#21069)
    ## Why
    
    Large hook outputs can enter model-visible context through hook-specific
    paths such as `additionalContext` and `Stop` continuation prompts.
    Without a dedicated cap, one hook can inject a large blob directly into
    conversation history instead of leaving a bounded preview for the model
    and preserving the full text elsewhere.
    
    ## What
    
    - spill hook text once it exceeds a fixed `2_500`-token budget,
    preserving the full output on disk and leaving a head/tail preview plus
    saved path in context
    - add shared hook-output spilling under
    `CODEX_HOME/hook_outputs/<thread_id>/<uuid>.txt`
    - apply the cap to both `additionalContext`, `feedback_message`, and
    `Stop` continuation fragments
  • codex: migrate (more) app-server thread history reads to ThreadStore (#20575)
    Migrate token usage replay, rollback responses, and detached review
    setup (a special case of forking) to be served from ThreadStore reads
    rather direct rollout files.
    
    - replay restored token usage from already-loaded `RolloutItem` history
    instead of reopening `Thread.path`
    - rebuild rollback responses from loaded `ThreadStore` snapshots and
    history
    - start detached reviews from store-backed parent history and stored
    review-thread metadata
    - remove obsolete app-server rollout-summary helper code that became
    dead after the store-backed migration
    - preserve response/notification ordering for resume, fork, rollback,
    and detached review flows
    - add integration test coverage for the affected paths
  • Add plugin ID to skill analytics (#20923)
    ## Summary
    - thread plugin skill roots through the skills loader with their plugin
    ID
    - store plugin ID on loaded skill metadata for plugin-provided skills
    - include plugin ID on skill invocation analytics events
    
    ## Test plan
    - cargo check -p codex-core-skills
    - cargo check -p codex-core -p codex-core-plugins -p codex-analytics
    - cargo check -p codex-tui
    - cargo check -p codex-plugin -p codex-core -p codex-core-plugins -p
    codex-analytics
    - cargo check -p codex-app-server
    - cargo test -p codex-analytics
    - HOME=/private/tmp/codex-empty-home cargo test -p codex-core-skills
    - just fix -p codex-core-skills
    - just fix -p codex-analytics
    - just fix -p codex-core-plugins
    - just fix -p codex-core
    - just fmt
    - git diff --check
  • codex: route metadata updates through ThreadStore (#20576)
    - Route `thread/metadata/update` through
    `ThreadStore::update_thread_metadata`.
    - Add `LocalThreadStore` git metadata patch support for set, partial
    update, and clear semantics.
    - Add some unit tests for the new thread store code
    - Remove a lot of dead code/tests!
  • [codex-analytics] add item lifecycle timing (#20514)
    ## Why
    
    Tool families already disagree on what their existing `duration` fields
    mean, so lifecycle latency should live on the shared item envelope
    instead of being inferred from per-tool execution fields. Carrying that
    envelope through app-server notifications gives downstream consumers one
    reusable timing signal without pretending every tool has the same
    execution semantics.
    
    ## What changed
    
    - Adds `started_at_ms` to core `ItemStartedEvent` values and
    `completed_at_ms` to core `ItemCompletedEvent` values.
    - Populates those timestamps in the shared session lifecycle emitters,
    so protocol-native items get timing without each producer tracking its
    own clock state.
    - Exposes `startedAtMs` on app-server `item/started` notifications and
    `completedAtMs` on `item/completed` notifications.
    - Maps the lifecycle timestamps through the app-server boundary while
    leaving legacy-converted notifications nullable when no lifecycle
    timestamp exists.
    - Regenerates the app-server JSON schema and TypeScript fixtures for the
    notification-envelope change and updates downstream fixtures that
    construct those notifications directly.
    - Extends the existing web-search and image-generation integration flows
    to assert the new lifecycle timestamps on the native item events.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-tui -p codex-exec
    -p codex-app-server-client`
    - `cargo test -p codex-core --test all web_search_item_is_emitted`
    - `cargo test -p codex-core --test all
    image_generation_call_event_is_emitted`
    - `cargo test -p codex-app-server-protocol`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/20514).
    * #18748
    * #18747
    * #17090
    * #17089
    * __->__ #20514
  • Make realtime sideband startup async (#20715)
    ## Summary
    
    Moves the WebRTC realtime sideband websocket join out of the voice start
    critical path. Call creation still posts the SDP offer and session
    config synchronously so the client gets the SDP answer, but the sideband
    websocket now connects in the input task async and doesn't block
    conversation state installation.
    
    This lets the normal realtime input channels buffer text, handoff
    output, and audio while the WebRTC sideband websocket is connecting. If
    the sideband join fails while the conversation is still active, the task
    sends a RealtimeEvent::Error through the existing events_tx / fanout
    path.
    
    To rephrase this:
    * No longer blocked on sideband: the client can receive the SDP answer
    earlier, set up the WebRTC peer connection, and let the media leg
    progress while the sideband websocket joins.
    * Still blocked on sideband: queued text, handoff output, and sideband
    server events cannot flow until connect_webrtc_sideband(...).await
    finishes and then run_realtime_input_task(...) starts
    
    ## Validation
    
    - `env CODEX_SKIP_VENDORED_BWRAP=1 cargo test --manifest-path
    codex-rs/Cargo.toml -p codex-core --test all
    conversation_webrtc_start_posts_generated_session`
    
    `CODEX_SKIP_VENDORED_BWRAP=1` is needed in this local environment
    because `libcap.pc` is not installed for the vendored bubblewrap build.
    
    ## Testing
    I tested this locally by running `cargo run -p codex-cli --bin codex --
    --enable realtime_conversation` and invoking `/realtime`. Then, we get
    logs emitted in `~/.codex/log/codex-tui.log`.
    
    ### Before the Change
    Logging commit
    (https://github.com/openai/codex/commit/c0299e6edf1222fa0c43c1796e4811976c26fecd)
    ```
    2026-05-04T16:06:09.251956Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: starting realtime conversation
    2026-05-04T16:06:09.251980Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: creating realtime call transport="webrtc"
    2026-05-04T16:06:10.365722Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: realtime call created; sdp answer ready transport="webrtc" call_id=rtc_u0_Dbq65nhak5eLjQZ73yhAy elapsed_ms=1113 total_elapsed_ms=1113
    2026-05-04T16:06:10.365843Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: connecting realtime sideband websocket call_id=rtc_u0_Dbq65nhak5eLjQZ73yhAy
    2026-05-04T16:06:10.784528Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: connected realtime sideband websocket call_id=rtc_u0_Dbq65nhak5eLjQZ73yhAy elapsed_ms=418 total_elapsed_ms=1532
    2026-05-04T16:06:10.784665Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: realtime conversation started
    ```
    
    ### After the Change
    Logging commit
    (https://github.com/openai/codex/commit/c8b00ac21adf4f8dd1fe3a81403a2bb6183fe13b)
    ```
    2026-05-04T15:41:24.080363Z  INFO ... codex_core::realtime_conversation: starting realtime conversation
    2026-05-04T15:41:24.080434Z  INFO ... codex_core::realtime_conversation: creating realtime call transport="webrtc"
    2026-05-04T15:41:25.106906Z  INFO ... codex_core::realtime_conversation: realtime call created; sdp answer ready transport="webrtc" call_id=rtc_u0_Dbpi8nhak5eLjQZ73yhAy elapsed_ms=1026 total_elapsed_ms=1026
    2026-05-04T15:41:25.107067Z  INFO ... codex_core::realtime_conversation: spawned realtime sideband connection task transport="webrtc" total_elapsed_ms=1026
    2026-05-04T15:41:25.107160Z  INFO ... codex_core::realtime_conversation: realtime conversation started
    2026-05-04T15:41:25.107185Z  INFO codex_core::realtime_conversation: connecting realtime sideband websocket call_id=rtc_u0_Dbpi8nhak5eLjQZ73yhAy
    2026-05-04T15:41:25.107352Z  INFO ... codex_core::realtime_conversation: sent realtime sdp answer to client
    2026-05-04T15:41:26.076685Z  INFO codex_core::realtime_conversation: connected realtime sideband websocket call_id=rtc_u0_Dbpi8nhak5eLjQZ73yhAy elapsed_ms=969 total_elapsed_ms=1996
    2026-05-04T15:41:26.573893Z  INFO codex_core::realtime_conversation: realtime session updated realtime_session_id=sess_u0_Dbpi8nhak5eLjQZ73yhAy
    2026-05-04T15:41:26.573970Z  INFO codex_core::realtime_conversation: received realtime conversation event event=SessionUpdated { ... }
    ```
    
    ### Conclusion
    Here we see that we saved about a half a second in conversation startup
    (1532ms -> 969ms). This also checks out with my sanity tests; I was
    seeing at most a second of saving.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add reasoning effort to turn tracing spans (#20060)
    Why
    #19432 added token usage to the turn and response spans. This follow-up
    adds the configured reasoning effort so performance traces can be
    filtered by model effort.
    
    [example
    trace](https://openai.datadoghq.com/apm/trace/1ff708a87159ff4898bdc8bd6091ec18?graphType=waterfall&shouldShowLegend=true&spanID=6596351544047485652&traceQuery=)
    <img width="533" height="434" alt="Screenshot 2026-04-28 at 3 52 12 PM"
    src="https://github.com/user-attachments/assets/77ef32fc-d7cd-4eec-87b4-26c6798f1af8"
    />
    
    
    What Changed
    - Adds `codex.turn.reasoning_effort` to the turn span.
    - Adds `codex.request.reasoning_effort` to `handle_responses`.
    - Extends the span test to cover explicit `high` effort with token
    usage.
    
    Testing
    - `cargo test -p codex-core
    turn_and_completed_response_spans_record_token_usage`
    - `cargo test -p codex-otel`
    - `just fmt`
    - `just fix -p codex-core`
    - `just fix -p codex-otel`
  • core: fix apply_patch request permissions test (#21060)
    ## Why
    
    The Bazel test coverage change exposed
    `approved_folder_write_request_permissions_unblocks_later_apply_patch`,
    and `rust-ci-full.yml` showed the same test failing on `main` on macOS.
    There were two separate classes of problems here.
    
    ### Clean CI failure
    
    The test emits an `apply_patch` tool call, but its config did not enable
    the `apply_patch` tool, so the mocked response completed without an
    `apply-patch-call` output. After enabling the tool, the same path also
    needs the aggregate `codex-core` test binary to dispatch
    `--codex-run-as-fs-helper`; sandboxed `apply_patch` uses that helper
    under macOS Seatbelt.
    
    The test now also canonicalizes the temporary patch target before
    building the patch payload so the path matches normalized grants on
    macOS, where `/var` paths often normalize to `/private/var`.
    
    ### Local/enterprise config isolation
    
    The core test harness now builds its default test config with managed
    config disabled, so host-managed enterprise config cannot alter these
    tests. The request-permissions turns in this test also explicitly use
    the user reviewer path, keeping the assertions focused on
    `request_permissions` behavior rather than reviewer defaults from the
    host.
    
    ## What Changed
    
    - Enable `apply_patch` in
    `approved_folder_write_request_permissions_unblocks_later_apply_patch`.
    - Teach the core integration test binary to dispatch
    `CODEX_FS_HELPER_ARG1`, matching the existing apply-patch and
    linux-sandbox dispatch paths.
    - Canonicalize the tempdir-backed patch target before creating the
    patch.
    - Ignore managed config in default core test configs and explicitly pin
    this test to `ApprovalsReviewer::User`.
    
    ## Verification
    
    Run outside the Codex app sandbox because these macOS tests
    intentionally spawn Seatbelt:
    
    - `cargo test -p codex-core
    approved_folder_write_request_permissions_unblocks_later_apply_patch`
    - `cargo test -p codex-core
    approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args`
  • core: preserve last model ids in feedback tags (#21026)
    ## Why
    
    Feedback reports do not currently surface a direct pointer to the last
    model call, so investigations may require searching through many
    requests in a session to find the bad response. Preserve the last
    model-side IDs at response-stream time so immediate feedback reports
    carry that breadcrumb.
    
    ## What changed
    
    - Record `last_model_request_id` when a Responses stream exposes an
    upstream request ID.
    - Record `last_model_response_id` when the model response completes.
    - Add unit coverage for the emitted feedback tags.
    
    ## Verification
    
    - `cargo test -p codex-core
    client::tests::response_stream_records_last_model_feedback_ids`
  • Use MCP server instructions in deferred namespace descriptions (#21053)
    ## Why
    
    MCP servers can provide `instructions` that explain what their tools are
    for. Directly exposed MCP namespaces already use those instructions when
    a connector description is not available, but deferred `tool_search`
    results did not preserve that fallback. The direct path falls back from
    connector metadata to server instructions, while the deferred path only
    carried `connector_description` and otherwise fell back to generic
    namespace text.
    
    That meant a plain MCP server could provide useful model-facing guidance
    and still appear as `Tools in the X namespace.` whenever it was
    discovered lazily through `tool_search`.
    
    ## What changed
    
    - Store one model-facing `namespace_description` on `ToolInfo`, using
    connector descriptions for connector-backed tools and server
    instructions for plain MCP servers.
    - Thread that namespace description through the `tool_search` source
    list, search indexing, and returned namespace metadata.
    - Add an end-to-end regression test for deferred non-app MCP search
    results exposing server instructions as the namespace description.
    
    ## Verification
    
    - `cargo test -p codex-tools
    search_tool_description_lists_each_mcp_source_once --lib`
    - `cargo test -p codex-core --test all
    tool_search_uses_non_app_mcp_server_instructions_as_namespace_description`
  • feat(tui): improve TUI keymap coverage (#20798)
    ## Summary
    - normalize terminal-emitted C0 control characters through configurable
    editor keymaps, covering raw control-key fallbacks like
    Shift+Enter-as-LF in terminals from #20555 and #20898, plus part of the
    modified-Enter behavior in #20580
    - add default-unbound keymap actions for toggling Fast mode and killing
    the current composer line, giving #20698 users a configurable zsh-style
    Ctrl+U option without changing the existing default Ctrl+U behavior
    - wire the new actions through gated /keymap picker entries, schema
    generation, and snapshot coverage
    
    Fixes #20555.
    Fixes #20898.
    
    ## Testing
    - just write-config-schema
    - just fmt
    - cargo test -p codex-config
    - cargo test -p codex-tui keymap::tests
    - cargo test -p codex-tui bottom_pane::textarea::tests
    - cargo test -p codex-tui keymap_setup::tests
    - cargo insta pending-snapshots
    - just fix -p codex-tui
    - git diff --check
    - just argument-comment-lint
  • state: pass state db handles through consumers (#20561)
    ## Why
    
    SQLite state was still being opened from consumer paths, including lazy
    `OnceCell`-backed thread-store call sites. That let one process
    construct multiple state DB connections for the same Codex home, which
    makes SQLite lock contention and `database is locked` failures much
    easier to hit.
    
    State DB lifetime should be chosen by main-like entrypoints and tests,
    then passed through explicitly. Consumers should use the supplied
    `Option<StateDbHandle>` or `StateDbHandle` and keep their existing
    filesystem fallback or error behavior when no handle is available.
    
    The startup path also needs to keep the rollout crate in charge of
    SQLite state initialization. Opening `codex_state::StateRuntime`
    directly bypasses rollout metadata backfill, so entrypoints should
    initialize through `codex_rollout::state_db` and receive a handle only
    after required rollout backfills have completed.
    
    ## What Changed
    
    - Initialize the state DB in main-like entrypoints for CLI, TUI,
    app-server, exec, MCP server, and the thread-manager sample.
    - Pass `Option<StateDbHandle>` through `ThreadManager`,
    `LocalThreadStore`, app-server processors, TUI app wiring, rollout
    listing/recording, personality migration, shell snapshot cleanup,
    session-name lookup, and memory/device-key consumers.
    - Remove the lazy local state DB wrapper from the thread store so
    non-test consumers use only the supplied handle or their existing
    fallback path.
    - Make `codex_rollout::state_db::init` the local state startup path: it
    opens/migrates SQLite, runs rollout metadata backfill when needed, waits
    for concurrent backfill workers up to a bounded timeout, verifies
    completion, and then returns the initialized handle.
    - Keep optional/non-owning SQLite helpers, such as remote TUI local
    reads, as open-only paths that do not run startup backfill.
    - Switch app-server startup from direct
    `codex_state::StateRuntime::init` to the rollout state initializer so
    app-server cannot skip rollout backfill.
    - Collapse split rollout lookup/list APIs so callers use the normal
    methods with an optional state handle instead of `_with_state_db`
    variants.
    - Restore `getConversationSummary(ThreadId)` to delegate through
    `ThreadStore::read_thread` instead of a LocalThreadStore-specific
    rollout path special case.
    - Keep DB-backed rollout path lookup keyed on the DB row and file
    existence, without imposing the filesystem filename convention on
    existing DB rows.
    - Verify readable DB-backed rollout paths against `session_meta.id`
    before returning them, so a stale SQLite row that points at another
    thread's JSONL falls back to filesystem search and read-repairs the DB
    row.
    - Keep `debug prompt-input` filesystem-only so a one-off debug command
    does not initialize or backfill SQLite state just to print prompt input.
    - Keep goal-session test Codex homes alive only in the goal-specific
    helper, rather than leaking tempdirs from the shared session test
    helper.
    - Update tests and call sites to pass explicit state handles where DB
    behavior is expected and explicit `None` where filesystem-only behavior
    is intended.
    
    ## Validation
    
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p
    codex-rollout -p codex-thread-store -p codex-app-server -p codex-core -p
    codex-tui -p codex-exec -p codex-cli --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout state_db_`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout try_init_ -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo clippy -p
    codex-rollout --lib -- -D warnings`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store
    read_thread_falls_back_when_sqlite_path_points_to_another_thread --
    --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    shell_snapshot`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all personality_migration`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find::find_prefers_sqlite_path_by_id --
    --nocapture`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    interrupt_accounts_active_goal_before_pausing`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server get_auth_status -- --test-threads=1`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server --lib`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p codex-rollout
    -p codex-app-server --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout
    -p codex-thread-store -p codex-core -p codex-app-server -p codex-tui -p
    codex-exec -p codex-cli`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout -p
    codex-app-server`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p
    codex-rollout`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-core`
    - `just argument-comment-lint -p codex-core`
    - `just argument-comment-lint -p codex-rollout`
    
    Focused coverage added in `codex-rollout`:
    
    - `recorder::tests::state_db_init_backfills_before_returning` verifies
    the rollout metadata row exists before startup init returns.
    - `state_db::tests::try_init_waits_for_concurrent_startup_backfill`
    verifies startup waits for another worker to finish backfill instead of
    disabling the handle for the process.
    -
    `state_db::tests::try_init_times_out_waiting_for_stuck_startup_backfill`
    verifies startup does not hang indefinitely on a stuck backfill lease.
    -
    `tests::find_thread_path_accepts_existing_state_db_path_without_canonical_filename`
    verifies DB-backed lookup accepts valid existing rollout paths even when
    the filename does not include the thread UUID.
    -
    `tests::find_thread_path_falls_back_when_db_path_points_to_another_thread`
    verifies DB-backed lookup ignores a stale row whose existing path
    belongs to another thread and read-repairs the row after filesystem
    fallback.
    
    Focused coverage updated in `codex-core`:
    
    - `rollout_list_find::find_prefers_sqlite_path_by_id` now uses a
    DB-preferred rollout file with matching `session_meta.id`, so it still
    verifies that valid SQLite paths win without depending on stale/empty
    rollout contents.
    
    `cargo test -p codex-app-server thread_list_respects_search_term_filter
    -- --test-threads=1 --nocapture` was attempted locally but timed out
    waiting for the app-server test harness `initialize` response before
    reaching the changed thread-list code path.
    
    `bazel test //codex-rs/thread-store:thread-store-unit-tests
    --test_output=errors` was attempted locally after the thread-store fix,
    but this container failed before target analysis while fetching `v8+`
    through BuildBuddy/direct GitHub. The equivalent local crate coverage,
    including `cargo test -p codex-thread-store`, passes.
    
    A plain local `cargo check -p codex-rollout -p codex-app-server --tests`
    also requires system `libcap.pc` for `codex-linux-sandbox`; the
    follow-up app-server check above used `CODEX_SKIP_VENDORED_BWRAP=1` in
    this container.
  • Prepare selected environment plumbing (#20669)
    ## Why
    This is a prep PR in the multi-environment process-tool stack. It
    separates ownership/config cleanup from the behavior change that teaches
    process tools to route by selected environment, so the follow-up PR can
    focus on model-facing `environment_id` behavior.
    
    ## Stack
    1. https://github.com/openai/codex/pull/20646 - `EnvironmentContext`
    rendering for selected environments
    2. https://github.com/openai/codex/pull/20669 - selected-environment
    ownership and tool config prep (this PR)
    3. https://github.com/openai/codex/pull/20647 - process-tool
    `environment_id` routing
    
    ## What Changed
    - keep the resolved turn environment list wrapped in
    `ResolvedTurnEnvironments` through `TurnContext` instead of unwrapping
    it back to a raw `Vec`
    - add `TurnContext::resolve_path_against` so cwd-relative path
    resolution has one shared helper
    - replace the old tool config boolean with `ToolEnvironmentMode::{None,
    Single, Multiple}`
    
    ## Testing
    - Tests not run locally; this prep refactor is covered by GitHub CI for
    the stack.
    
    Co-authored-by: Codex <noreply@openai.com>
  • Unify skip-review handling for approval_mode = "approve" (#20750)
    ## Summary
    - Treat `approval_mode = "approve"` as skip-review across all permission
    modes.
    - Remove the mode-specific split in the MCP auto-approval gate so
    approved tools bypass review consistently.
    - Expand regression coverage in the shared MCP helper and the core
    tool-call flow.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core
    approve_mode_skips_arc_and_guardian_in_every_permission_mode`
    - `git diff --check`
    - Full `cargo test -p codex-core` was also attempted, but the suite hit
    an unrelated pre-existing stack overflow in an existing multi-agent test
  • core: share responses request builder with compact requests (#20989)
    ## Why
    
    `ModelClientSession` and `compact_conversation_history()` were still
    rebuilding the same `ResponsesApiRequest` fields separately. That
    duplication makes it easy for normal `/responses` turns and compact
    requests to drift when request-shape changes land later, which is
    exactly the kind of cache-affecting divergence we want to avoid.
    
    This follow-up keeps the scope small by extracting the shared
    request-construction logic into one helper and using it from both paths.
    
    ## What changed
    
    - move `ResponsesApiRequest` construction into a shared
    `ModelClient::build_responses_request(...)` helper in
    `core/src/client.rs`
    - update the normal `/responses` streaming path to call that helper
    instead of the old `ModelClientSession`-local implementation
    - update `compact_conversation_history()` to derive its compact payload
    from the same helper so `model`, `instructions`, `input`, `tools`,
    `parallel_tool_calls`, `reasoning`, and `text` stay aligned with normal
    request building
    - add a unit test covering the shared helper's prompt cache key,
    installation metadata, and `service_tier` behavior
    
    ## Verification
    
    - `cargo test -p codex-core
    build_responses_request_sets_shared_cache_and_metadata_fields`
    - `cargo test -p codex-core --test all
    remote_compact_v2_reuses_context_compaction_for_followups`
    
    ## Docs
    
    No docs update needed.
  • Keep paused goals paused on thread resume (#20790)
    ## Summary
    
    Early adopters of the `/goal` feature have provided feedback that they
    expect a goal they explicitly paused to remain paused when they resume a
    thread. Previously, resuming a thread would reactivate a paused goal.
    
    This PR keeps persisted goal status unchanged during thread resume. This
    honors the user feedback while also simplifying the core goal logic.
    
    Rather than have the core logic automatically resume a paused goal, that
    responsibility is transferred to the client. The TUI now detects a
    resumed thread with a paused goal and asks the user whether to `Resume
    goal` or `Leave paused`. The prompt appears only for quiet resume flows,
    so users who resume with an immediate prompt are not interrupted.
    
    <img width="544" height="111" alt="image"
    src="https://github.com/user-attachments/assets/0ac9de1c-6ee6-47ba-b223-c03c8eb4c192"
    />
  • feat: add remote compaction v2 Responses client path (#20773)
    ## Why
    
    This adds the `remote_compaction_v2` client path so remote compaction
    can run through the normal Responses stream and install a
    `context_compaction` item that trigger a compaction.
    
    The goal is to migrate some of the compaction logic on the client side
    
    We keeps the v2 transport behind a feature flag while letting follow-up
    requests reuse the compacted context instead of falling back to the
    legacy compaction item shape.
    
    ## What changed
    
    - add `ResponseItem::ContextCompaction` and refresh the generated
    app-server / schema / TypeScript fixtures that expose response items on
    the wire
    - add `core/src/compact_remote_v2.rs` to send compaction through the
    standard streamed Responses client, require exactly one
    `context_compaction` output item, and install that item into compacted
    history
    - route manual compact and auto-compaction through the v2 path when
    `remote_compaction_v2` is enabled, while keeping the existing remote
    compaction path as the fallback
    - preserve the new item type across history retention, follow-up request
    construction, telemetry, rollout persistence, and rollout-trace
    normalization
    - add targeted coverage for the feature flag, `context_compaction`
    serialization, rollout-trace normalization, and remote-compaction
    follow-up behavior
    
    ## Verification
    
    - added protocol tests for `context_compaction`
    serialization/deserialization in `protocol/src/models.rs`
    - added rollout-trace coverage for `context_compaction` normalization in
    `rollout-trace/src/reducer/conversation_tests.rs`
    - added remote compaction integration coverage for v2 follow-up reuse
    and mixed compaction output streams in
    `core/tests/suite/compact_remote.rs`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: support template interpolation in multi-agent usage hints (#20973)
    ## Why
    
    `multi_agent_v2` usage hints sometimes need to reference resolved config
    values such as the effective thread limit. Those values only exist after
    config layering, defaulting, and feature materialization, so the raw
    TOML alone was not enough to render them.
    
    ## What changed
    
    - allow
    `features.multi_agent_v2.{usage_hint_text,root_agent_usage_hint_text,subagent_usage_hint_text}`
    to use `{{ ... }}` placeholders backed by the materialized effective
    config
    - fail config loading with a targeted error when a referenced
    placeholder does not exist or does not resolve to a scalar value
    - move resolved-config materialization into a shared helper so config
    interpolation and config-lock export/replay both serialize the same
    resolved feature, memory, and agent settings
    
    ## Example
    ```
    [features.multi_agent_v2]
    enabled = true
    usage_hint_text = "lorem {{ features.multi_agent_v2.max_concurrent_threads_per_session }} ipsum"
    ```
    gets rendered as 
    ```
            "description": String("... \lorem 4 ipsum"),
    ```
  • [codex] Emit MCP tool calls as turn items (#20677)
    ## Why
    
    `McpToolCall` was still an app-server item synthesized from deprecated
    legacy begin/end events. Recent item migrations moved this ownership
    into core `TurnItem`s, so MCP tool calls now follow the same canonical
    lifecycle and leave legacy events as compatibility fanout.
    
    Keeping the core item close to the v2 `ThreadItem::McpToolCall` shape
    also avoids spreading MCP result semantics across app-server conversion
    code. Core now owns whether a completed call is `completed` or `failed`,
    and whether the payload is a tool result or an error.
    
    ## What changed
    
    - Added core `TurnItem::McpToolCall` with flattened `server`, `tool`,
    `arguments`, `status`, `result`, and `error` fields.
    - Updated MCP tool call emitters, including MCP resource tools, to emit
    `ItemStarted`/`ItemCompleted` around directly constructed core MCP
    items.
    - Updated app-server v2 conversion to project the core MCP item into
    `ThreadItem::McpToolCall` without deriving status or splitting `Result`
    locally.
    - Ignored live deprecated MCP legacy fanout in app-server v2 to avoid
    duplicate item notifications, while keeping thread history replay on the
    legacy event path.
    
    ## Verification
    
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core --lib mcp_tool_call`
    - `cargo check -p codex-app-server`
    - `cargo test -p codex-app-server
    mcp_tool_call_completion_notification_contains_truncated_large_result`
  • [tool_suggest] More prompt polishes. (#20566)
    Tool suggest still misfires when model needs tool_search, updating the
    prompts to further disambiguate it:
    
    - [x] rename it from `tool_suggest` to `request_plugin_install`
    - [x] rephrase "suggestion" to "install" in the tool descriptions.
    - [x] disambiguate "the tool" vs "the plugin/connector". 
    
    Tested with the Codex App and verified it still works.
  • Prune unused code-mode globals (#20542)
    Hide Atomics, SharedArrayBuffer, and WebAssembly from the code-mode
    runtime since the harness does not expose worker support or need those
    APIs.
  • Surface multi-environment choices in environment context (#20646)
    ## Why
    The model needs a way to see which environments are available during a
    multi-environment turn without changing the legacy single-environment
    prompt surface or pulling replay/persistence changes into the same
    review.
    
    ## Stack
    1. https://github.com/openai/codex/pull/20646 - `EnvironmentContext`
    rendering for selected environments (this PR)
    2. https://github.com/openai/codex/pull/20669 - selected-environment
    ownership and tool config prep
    3. https://github.com/openai/codex/pull/20647 - process-tool
    `environment_id` routing
    
    ## What Changed
    - extend `environment_context` so multi-environment turns render an
    `<environments>` block with the selected environment ids and cwd values
    - keep zero- and single-environment turns on the existing cwd-only
    render path
    - keep replay and persistence paths on the legacy surface for now so
    this PR stays scoped to live prompt rendering
    - add focused coverage in
    `codex-rs/core/src/context/environment_context_tests.rs`
    
    ## Testing
    - CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>