Commit Graph

3718 Commits

  • Add text element metadata to protocol, app server, and core (#9331)
    The second part of breaking up PR
    https://github.com/openai/codex/pull/9116
    
    Summary:
    
    - Add `TextElement` / `ByteRange` to protocol user inputs and user
    message events with defaults.
    - Thread `text_elements` through app-server v1/v2 request handling and
    history rebuild.
    - Preserve UI metadata only in user input/events (not `ContentItem`)
    while keeping local image attachments in user events for rehydration.
    
    Details:
    
    - Protocol: `UserInput::Text` carries `text_elements`;
    `UserMessageEvent` carries `text_elements` + `local_images`.
    Serialization includes empty vectors for backward compatibility.
    - app-server-protocol: v1 defines `V1TextElement` / `V1ByteRange` in
    camelCase with conversions; v2 uses its own camelCase wrapper.
    - app-server: v1/v2 input mapping includes `text_elements`; thread
    history rebuilds include them.
    - Core: user event emission preserves UI metadata while model history
    stays clean; history replay round-trips the metadata.
  • fix: send non-null content on elicitation Accept (#9196)
    ## Summary
    
    - When a user accepts an MCP elicitation request, send `content:
    Some(json!({}))` instead of `None`
    - MCP servers that use elicitation expect content to be present when
    action is Accept
    - This matches the expected behavior shown in tests at
    `exec-server/tests/common/lib.rs:171`
    
    ## Root Cause
    
    In `codex-rs/core/src/codex.rs`, the `resolve_elicitation` function
    always sent `content: None`:
    
    ```rust
    let response = ElicitationResponse {
        action,
        content: None,  // Always None, even for Accept
    };
    ```
    
    ## Fix
    
    Send an empty object when accepting:
    
    ```rust
    let content = match action {
        ElicitationAction::Accept => Some(serde_json::json!({})),
        ElicitationAction::Decline | ElicitationAction::Cancel => None,
    };
    ```
    
    ## Test plan
    
    - [x] Code compiles with `cargo check -p codex-core`
    - [x] Formatted with `just fmt`
    - [ ] Integration test `accept_elicitation_for_prompt_rule` (requires
    MCP server binary)
    
    Fixes #9053
  • Revert empty paste image handling (#9318)
    Revert #9049 behavior so empty paste events no longer trigger a
    clipboard image read.
  • [search] allow explicitly disabling web search (#9249)
    moving `web_search` rollout serverside, so need a way to explicitly
    disable search + signal eligibility from the client.
    
    - Add `x‑oai‑web‑search‑eligible` header that signifies whether the
    request can have web search.
    - Only attach the `web_search` tool when the resolved `WebSearchMode` is
    `Live` or `Cached`.
  • Support SKILL.toml file. (#9125)
    We’re introducing a new SKILL.toml to hold skill metadata so Codex can
    deliver a richer Skills experience.
    
    Initial focus is the interface block:
    ```
    [interface]
    display_name = "Optional user-facing name"
    short_description = "Optional user-facing description"
    icon_small = "./assets/small-400px.png"
    icon_large = "./assets/large-logo.svg"
    brand_color = "#3B82F6"
    default_prompt = "Optional surrounding prompt to use the skill with"
    ```
    
    All fields are exposed via the app server API.
    display_name and short_description are consumed by the TUI.
  • Revert recent styling change for input prompt placeholder text (#9307)
    A recent change in commit ccba737d26 modified the styling of the
    placeholder text (e.g. "Implement {feature}") in the input box of the
    CLI, changing it from non-italic to italic. I think this was likely
    unintentional. It results in a bad display appearance on some terminal
    emulators, and several users have complained about it.
    
    This change switches back to non-italic styling, restoring the older
    behavior.
    
    It addresses #9262
  • nit: clean unified exec background processes (#9304)
    To fix the occurences where the End event is received after the listener
    stopped listenning
  • revert: remove pre-Landlock bind mounts apply (#9300)
    **Description**
    
    This removes the pre‑Landlock read‑only bind‑mount step from the Linux
    sandbox so filesystem restrictions rely solely on Landlock again.
    `mounts.rs` is kept in place but left unused. The linux‑sandbox README
    is updated to match the new behavior and manual test expectations.
  • fix(exec): improve stdin prompt decoding (#9151)
    Fixes #8733.
    
    - Read prompt from stdin as raw bytes and decode more helpfully.
    - Strip UTF-8 BOM; decode UTF-16LE/UTF-16BE when a BOM is present.
    - For other non-UTF8 input, fail with an actionable message (offset +
    iconv hint).
    
    Tests: `cargo test -p codex-exec`.
  • Propagate MCP disabled reason (#9207)
    Indicate why MCP servers are disabled when they are disabled by
    requirements:
    
    ```
    ➜  codex git:(main) ✗ just codex mcp list
    cargo run --bin codex -- "$@"
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
         Running `target/debug/codex mcp list`
    Name         Command          Args  Env  Cwd  Status                                                                  Auth
    docs         docs-mcp         -     -    -    disabled: requirements (MDM com.openai.codex:requirements_toml_base64)  Unsupported
    hello_world  hello-world-mcp  -     -    -    disabled: requirements (MDM com.openai.codex:requirements_toml_base64)  Unsupported
    
    ➜  codex git:(main) ✗ just c
    cargo run --bin codex -- "$@"
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.90s
         Running `target/debug/codex`
    ╭─────────────────────────────────────────────╮
    │ >_ OpenAI Codex (v0.0.0)                    │
    │                                             │
    │ model:     gpt-5.2 xhigh   /model to change │
    │ directory: ~/code/codex/codex-rs            │
    ╰─────────────────────────────────────────────╯
    
    /mcp
    
    🔌  MCP Tools
    
      • No MCP tools available.
    
      • docs (disabled)
        • Reason: requirements (MDM com.openai.codex:requirements_toml_base64)
    
      • hello_world (disabled)
        • Reason: requirements (MDM com.openai.codex:requirements_toml_base64)
    ```
  • Changed codex resume --last to honor the current cwd (#9245)
    This PR changes `codex resume --last` to work consistently with `codex
    resume`. Namely, it filters based on the cwd when selecting the last
    session. It also supports the `--all` modifier as an override.
    
    This addresses #8700
  • feat: add agent roles to collab tools (#9275)
    Add `agent_type` parameter to the collab tool `spawn_agent` that
    contains a preset to apply on the config when spawning this agent
  • fix: fallback to Landlock-only when user namespaces unavailable and set PR_SET_NO_NEW_PRIVS early (#9250)
    fixes https://github.com/openai/codex/issues/9236
    
    ### Motivation
    - Prevent sandbox setup from failing when unprivileged user namespaces
    are denied so Landlock-only protections can still be applied.
    - Ensure `PR_SET_NO_NEW_PRIVS` is set before installing seccomp and
    Landlock restrictions to avoid kernel `EPERM`/`LandlockRestrict`
    ordering issues.
    
    ### Description
    - Add `is_permission_denied` helper that detects `EPERM` /
    `PermissionDenied` from `CodexErr` to drive fallback logic.
    - In `apply_read_only_mounts` skip read-only bind-mount setup and return
    `Ok(())` when `unshare_user_and_mount_namespaces()` fails with
    permission-denied so Landlock rules can still be installed.
    - Add `set_no_new_privs()` and call it from
    `apply_sandbox_policy_to_current_thread` before installing seccomp
    filters and Landlock rules when disk or network access is restricted.
  • Add migration_markdown in model_info (#9219)
    Next step would be to clean Model Upgrade in model presets
    
    ---------
    
    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
  • Add text element metadata to types (#9235)
    Initial type tweaking PR to make the diff of
    https://github.com/openai/codex/pull/9116 smaller
    
    This should not change any behavior, just adds some fields to types
  • fix: increase timeout for release builds from 30 to 60 minutes (#9242)
    Windows builds have been tripping the 30 minute timeout. For sure, we
    need to improve this, but as a quick fix, let's just increase the
    timeout.
    
    Perhaps we should switch to `lto = "thin"` for release builds, at least
    for Windows:
    
    
    https://github.com/openai/codex/blob/3728db11b87cb8490bcf6bf2cdf0e13dcfb0c28b/codex-rs/Cargo.toml#L288
    
    See https://doc.rust-lang.org/cargo/reference/profiles.html#lto for
    details.
  • fix: eliminate unnecessary clone() for each SSE event (#9238)
    Given how many SSE events we get, seems worth fixing.
  • fix: correct linux sandbox uid/gid mapping after unshare (#9234)
    fixes https://github.com/openai/codex/issues/9233
    ## Summary
    - capture effective uid/gid before unshare for user namespace maps
    - pass captured ids into uid/gid map writer
    
    ## Testing
    - just fmt
    - just fix -p codex-linux-sandbox
    - cargo test -p codex-linux-sandbox
  • upgrade runners in rust-ci.yml to use the larger runners (#9106)
    Upgrades runners in rust-ci.yaml to larger runners
    
    ubuntu-24.04 (x64 and arm64) -> custom 16 core ubuntu 24.04 runners
    macos-14 -> mac0s-15-xlarge
    [TODO] windows (x64 and arm64) -> custom 16 core windows runners
  • add WebSearchMode enum (#9216)
    ### What
    Add `WebSearchMode` enum (disabled, cached live, defaults to cached) to
    config + V2 protocol. This enum takes precedence over legacy flags:
    `web_search_cached`, `web_search_request`, and `tools.web_search`.
    
    Keep `--search` as live.
    
    ### Tests
    Added tests
  • fix(tui): disable double-press quit shortcut (#9220)
    Disables the default Ctrl+C/Ctrl+D double-press quit UX (keeps the code
    path behind a const) while we rethink the quit/interrupt flow.
    
    Tests:
    - just fmt
    - cargo clippy --fix --all-features --tests --allow-dirty --allow-no-vcs
    -p codex-tui
    - cargo test -p codex-tui --lib
  • fix(tui2): align Steer submit keys (#9218)
    - Remove legacy Ctrl+K queuing in tui2; Tab is the queue key.
    - Make Enter queue when Steer is disabled and submit immediately when
    Steer is enabled.
    - Add Steer keybinding docs on both tui and tui2 chat composers.
  • s/mcp_server_requirements/mcp_servers (#9212)
    A simple `s/mcp_server_requirements/mcp_servers/g` for an unreleased
    feature. @bolinfest correctly pointed out, it's already in
    `requirements.toml` so the `_requirements` is redundant.
  • Log headers in trace mode (#9214)
    To enable:
    
    ```
    export RUST_LOG="warn,codex_=trace"
    ```
    
    Sample: 
    ```
    Request completed method=POST url=https://chatgpt.com/backend-api/codex/responses status=200 OK headers={"date": "Wed, 14 Jan 2026 18:21:21 GMT", "transfer-encoding": "chunked", "connection": "keep-alive", "x-codex-plan-type": "business", "x-codex-primary-used-percent": "3", "x-codex-secondary-used-percent": "6", "x-codex-primary-window-minutes": "300", "x-codex-primary-over-secondary-limit-percent": "0", "x-codex-secondary-window-minutes": "10080", "x-codex-primary-reset-after-seconds": "9944", "x-codex-secondary-reset-after-seconds": "171121", "x-codex-primary-reset-at": "1768424824", "x-codex-secondary-reset-at": "1768586001", "x-codex-credits-has-credits": "False", "x-codex-credits-balance": "", "x-codex-credits-unlimited": "False", "x-models-etag": "W/\"7a7ffbc83c159dbd7a2a73aaa9c91b7a\"", "x-oai-request-id": "ffedcd30-6d8a-4c4d-be10-8ebb23c142c8", "x-envoy-upstream-service-time": "417", "x-openai-proxy-wasm": "v0.1", "cf-cache-status": "DYNAMIC", "set-cookie": "__cf_bm=xFKeaMbWNbKO5ZX.K5cJBhj34OA1QvnF_3nkdMThjlA-1768414881-1.0.1.1-uLpsE_BDkUfcmOMaeKVQmv_6_2ytnh_R3lO_il5N5K3YPQEkBo0cOMTdma6bK0Gz.hQYcIesFwKIJht1kZ9JKqAYYnjgB96hF4.sii2U3cE; path=/; expires=Wed, 14-Jan-26 18:51:21 GMT; domain=.chatgpt.com; HttpOnly; Secure; SameSite=None", "report-to": "{\"endpoints\":[{\"url\":\"https:\/\/a.nel.cloudflare.com\/report\/v4?s=4Kc7g4zUhKkIm3xHuB6ba4jyIUqqZ07ETwIPAYQASikRjA8JesbtUKDP9tSrZ5PnzWldaiSz5dZVQFI579LEsCMlMUSelTvmyQ8j4FbFDawi%2FprWZ5iRePiaSalr\"}],\"group\":\"cf-nel\",\"max_age\":604800}", "nel": "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}", "strict-transport-security": "max-age=31536000; includeSubDomains; preload", "x-content-type-options": "nosniff", "cross-origin-opener-policy": "same-origin-allow-popups", "referrer-policy": "strict-origin-when-cross-origin", "server": "cloudflare", "cf-ray": "9bdf270adc7aba3a-SEA"} version=HTTP/1.1
    ```
  • Get model on session configured (#9191)
    - Don't try to precompute model unless you know it from `config`
    - Block `/model` on session configured
    - Queue messages until session configured
    - show "loading" in status until session configured
  • fix: Emit response.completed immediately for Responses SSE (#9170)
    we see windows test failures like this:
    https://github.com/openai/codex/actions/runs/20930055601/job/60138344260.
    
    The issue is that SSE connections sometimes remain open after the
    completion event esp. for windows. We should emit the completion event
    and return immediately. this is consistent with the protocol:
    
    > The Model streams responses back in an SSE, which are collected until
    "completed" message and the SSE terminates
    
    from
    https://github.com/openai/codex/blob/dev/cc/fix-windows-test/codex-rs/docs/protocol_v1.md#L37.
    
    this helps us achieve parity with responses websocket logic here:
    https://github.com/openai/codex/blob/dev/cc/fix-windows-test/codex-rs/codex-api/src/endpoint/responses_websocket.rs#L220-L227.
  • feat: add collab prompt (#9208)
    Adding a prompt for collab tools. This is only for internal use and the
    prompt won't be gated for now as it is not stable yet.
    
    The goal of this PR is to provide the tool required to iterate on the
    prompt
  • feat: emit events around collab tools (#9095)
    Emit the following events around the collab tools. On the `app-server`
    this will be under `item/started` and `item/completed`
    ```
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabAgentSpawnBeginEvent {
        /// Identifier for the collab tool call.
        pub call_id: String,
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
        /// beginning.
        pub prompt: String,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabAgentSpawnEndEvent {
        /// Identifier for the collab tool call.
        pub call_id: String,
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the newly spawned agent, if it was created.
        pub new_thread_id: Option<ThreadId>,
        /// Initial prompt sent to the agent. Can be empty to prevent CoT leaking at the
        /// beginning.
        pub prompt: String,
        /// Last known status of the new agent reported to the sender agent.
        pub status: AgentStatus,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabAgentInteractionBeginEvent {
        /// Identifier for the collab tool call.
        pub call_id: String,
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the receiver.
        pub receiver_thread_id: ThreadId,
        /// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
        /// leaking at the beginning.
        pub prompt: String,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabAgentInteractionEndEvent {
        /// Identifier for the collab tool call.
        pub call_id: String,
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the receiver.
        pub receiver_thread_id: ThreadId,
        /// Prompt sent from the sender to the receiver. Can be empty to prevent CoT
        /// leaking at the beginning.
        pub prompt: String,
        /// Last known status of the receiver agent reported to the sender agent.
        pub status: AgentStatus,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabWaitingBeginEvent {
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the receiver.
        pub receiver_thread_id: ThreadId,
        /// ID of the waiting call.
        pub call_id: String,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabWaitingEndEvent {
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the receiver.
        pub receiver_thread_id: ThreadId,
        /// ID of the waiting call.
        pub call_id: String,
        /// Last known status of the receiver agent reported to the sender agent.
        pub status: AgentStatus,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabCloseBeginEvent {
        /// Identifier for the collab tool call.
        pub call_id: String,
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the receiver.
        pub receiver_thread_id: ThreadId,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, TS)]
    pub struct CollabCloseEndEvent {
        /// Identifier for the collab tool call.
        pub call_id: String,
        /// Thread ID of the sender.
        pub sender_thread_id: ThreadId,
        /// Thread ID of the receiver.
        pub receiver_thread_id: ThreadId,
        /// Last known status of the receiver agent reported to the sender agent before
        /// the close.
        pub status: AgentStatus,
    }
    ```
  • tui: double-press Ctrl+C/Ctrl+D to quit (#8936)
    ## Problem
    
    Codex’s TUI quit behavior has historically been easy to trigger
    accidentally and hard to reason
    about.
    
    - `Ctrl+C`/`Ctrl+D` could terminate the UI immediately, which is a
    common key to press while trying
      to dismiss a modal, cancel a command, or recover from a stuck state.
    - “Quit” and “shutdown” were not consistently separated, so some exit
    paths could bypass the
      shutdown/cleanup work that should run before the process terminates.
    
    This PR makes quitting both safer (harder to do by accident) and more
    uniform across quit
    gestures, while keeping the shutdown-first semantics explicit.
    
    ## Mental model
    
    After this change, the system treats quitting as a UI request that is
    coordinated by the app
    layer.
    
    - The UI requests exit via `AppEvent::Exit(ExitMode)`.
    - `ExitMode::ShutdownFirst` is the normal user path: the app triggers
    `Op::Shutdown`, continues
    rendering while shutdown runs, and only ends the UI loop once shutdown
    has completed.
    - `ExitMode::Immediate` exists as an escape hatch (and as the
    post-shutdown “now actually exit”
    signal); it bypasses cleanup and should not be the default for
    user-triggered quits.
    
    User-facing quit gestures are intentionally “two-step” for safety:
    
    - `Ctrl+C` and `Ctrl+D` no longer exit immediately.
    - The first press arms a 1-second window and shows a footer hint (“ctrl
    + <key> again to quit”).
    - Pressing the same key again within the window requests a
    shutdown-first quit; otherwise the
      hint expires and the next press starts a fresh window.
    
    Key routing remains modal-first:
    
    - A modal/popup gets first chance to consume `Ctrl+C`.
    - If a modal handles `Ctrl+C`, any armed quit shortcut is cleared so
    dismissing a modal cannot
      prime a subsequent `Ctrl+C` to quit.
    - `Ctrl+D` only participates in quitting when the composer is empty and
    no modal/popup is active.
    
    The design doc `docs/exit-confirmation-prompt-design.md` captures the
    intended routing and the
    invariants the UI should maintain.
    
    ## Non-goals
    
    - This does not attempt to redesign modal UX or make modals uniformly
    dismissible via `Ctrl+C`.
    It only ensures modals get priority and that quit arming does not leak
    across modal handling.
    - This does not introduce a persistent confirmation prompt/menu for
    quitting; the goal is to keep
      the exit gesture lightweight and consistent.
    - This does not change the semantics of core shutdown itself; it changes
    how the UI requests and
      sequences it.
    
    ## Tradeoffs
    
    - Quitting via `Ctrl+C`/`Ctrl+D` now requires a deliberate second
    keypress, which adds friction for
      users who relied on the old “instant quit” behavior.
    - The UI now maintains a small time-bounded state machine for the armed
    shortcut, which increases
      complexity and introduces timing-dependent behavior.
    
    This design was chosen over alternatives (a modal confirmation prompt or
    a long-lived “are you
    sure” state) because it provides an explicit safety barrier while
    keeping the flow fast and
    keyboard-native.
    
    ## Architecture
    
    - `ChatWidget` owns the quit-shortcut state machine and decides when a
    quit gesture is allowed
      (idle vs cancellable work, composer state, etc.).
    - `BottomPane` owns rendering and local input routing for modals/popups.
    It is responsible for
    consuming cancellation keys when a view is active and for
    showing/expiring the footer hint.
    - `App` owns shutdown sequencing: translating
    `AppEvent::Exit(ShutdownFirst)` into `Op::Shutdown`
      and only terminating the UI loop when exit is safe.
    
    This keeps “what should happen” decisions (quit vs interrupt vs ignore)
    in the chat/widget layer,
    while keeping “how it looks and which view gets the key” in the
    bottom-pane layer.
    
    ## Observability
    
    You can tell this is working by running the TUIs and exercising the quit
    gestures:
    
    - While idle: pressing `Ctrl+C` (or `Ctrl+D` with an empty composer and
    no modal) shows a footer
    hint for ~1 second; pressing again within that window exits via
    shutdown-first.
    - While streaming/tools/review are active: `Ctrl+C` interrupts work
    rather than quitting.
    - With a modal/popup open: `Ctrl+C` dismisses/handles the modal (if it
    chooses to) and does not
    arm a quit shortcut; a subsequent quick `Ctrl+C` should not quit unless
    the user re-arms it.
    
    Failure modes are visible as:
    
    - Quits that happen immediately (no hint window) from `Ctrl+C`/`Ctrl+D`.
    - Quits that occur while a modal is open and consuming `Ctrl+C`.
    - UI termination before shutdown completes (cleanup skipped).
    
    ## Tests
    
    - Updated/added unit and snapshot coverage in `codex-tui` and
    `codex-tui2` to validate:
      - The quit hint appears and expires on the expected key.
    - Double-press within the window triggers a shutdown-first quit request.
    - Modal-first routing prevents quit bypass and clears any armed shortcut
    when a modal consumes
        `Ctrl+C`.
    
    These tests focus on the UI-level invariants and rendered output; they
    do not attempt to validate
    real terminal key-repeat timing or end-to-end process shutdown behavior.
    
    ---
    Screenshot:
    <img width="912" height="740" alt="Screenshot 2026-01-13 at 1 05 28 PM"
    src="https://github.com/user-attachments/assets/18f3d22e-2557-47f2-a369-ae7a9531f29f"
    />
  • Use current model for review (#9179)
    Instead of having a hard-coded default review model, use the current
    model for running `/review` unless one is specified in the config.
    
    Also inherit current reasoning effort
  • feat: add support for read-only bind mounts in the linux sandbox (#9112)
    ### Motivation
    
    - Landlock alone cannot prevent writes to sensitive in-repo files like
    `.git/` when the repo root is writable, so explicit mount restrictions
    are required for those paths.
    - The sandbox must set up any mounts before calling Landlock so Landlock
    can still be applied afterwards and the two mechanisms compose
    correctly.
    
    ### Description
    
    - Add a new `linux-sandbox` helper `apply_read_only_mounts` in
    `linux-sandbox/src/mounts.rs` that: unshares namespaces, maps uids/gids
    when required, makes mounts private, bind-mounts targets, and remounts
    them read-only.
    - Wire the mount step into the sandbox flow by calling
    `apply_read_only_mounts(...)` before network/seccomp and before applying
    Landlock rules in `linux-sandbox/src/landlock.rs`.
  • feat: add auto refresh on thread listeners (#9105)
    This PR is in the scope of multi-agent work. 
    
    An agent (=thread) can now spawn other agents. Those other agents are
    not attached to any clients. We need a way to make sure that the clients
    are aware of the new threads to look at (for approval for example). This
    PR adds a channel to the `ThreadManager` that pushes the ID of those
    newly created agents such that the client (here the app-server) can also
    subscribe to those ones.
  • chore: clamp min yield time for empty write_stdin (#9156)
    After evals, 0 impact on performance
  • feat: return an error if the image sent by the user is a bad image (#9146)
    ## Before
    When we detect an `InvalidImageRequest`, we replace the image by a
    placeholder and keep going
    
    ## Now
    In such `InvalidImageRequest`, we check if the image is due to a user
    message or a tool call output. For tool call output we still replace it
    with a placeholder to avoid breaking the agentic loop bu tif this is
    because of a user message, we send an error to the user
  • fix: shell snapshot clean-up (#9155)
    Clean all shell snapshot files corresponding to sessions that have not
    been updated in 7 days
    Those files should never leak. The only known cases were it can leak are
    during non graceful interrupt of the process (`kill -9, `panic`, OS
    crash, ...)
  • feat: add threadId to MCP server messages (#9192)
    This favors `threadId` instead of `conversationId` so we use the same
    terms as https://developers.openai.com/codex/sdk/.
    
    To test the local build:
    
    ```
    cd codex-rs
    cargo build --bin codex
    npx -y @modelcontextprotocol/inspector ./target/debug/codex mcp-server
    ```
    
    I sent:
    
    ```json
    {
      "method": "tools/call",
      "params": {
        "name": "codex",
        "arguments": {
          "prompt": "favorite ls option?"
        },
        "_meta": {
          "progressToken": 0
        }
      }
    }
    ```
    
    and got:
    
    ```json
    {
      "content": [
        {
          "type": "text",
          "text": "`ls -lah` (or `ls -alh`) — long listing, includes dotfiles, human-readable sizes."
        }
      ],
      "structuredContent": {
        "threadId": "019bbb20-bff6-7130-83aa-bf45ab33250e"
      }
    }
    ```
    
    and successfully used the `threadId` in the follow-up with the
    `codex-reply` tool call:
    
    ```json
    {
      "method": "tools/call",
      "params": {
        "name": "codex-reply",
        "arguments": {
          "prompt": "what is the long versoin",
          "threadId": "019bbb20-bff6-7130-83aa-bf45ab33250e"
        },
        "_meta": {
          "progressToken": 1
        }
      }
    }
    ```
    
    whose response also has the `threadId`:
    
    ```json
    {
      "content": [
        {
          "type": "text",
          "text": "Long listing is `ls -l` (adds permissions, owner/group, size, timestamp)."
        }
      ],
      "structuredContent": {
        "threadId": "019bbb20-bff6-7130-83aa-bf45ab33250e"
      }
    }
    ```
    
    Fixes https://github.com/openai/codex/issues/3712.
  • chore: clarify default shell for unified_exec (#8997)
    The description of the `shell` arg for `exec_command` states the default
    is `/bin/bash`, but AFAICT it's the user's default shell.
    
    Default logic
    [here](https://github.com/openai/codex/blob/2a06d64bc996e4d848b95285700b195c2852a42f/codex-rs/core/src/tools/handlers/unified_exec.rs#L123).
    
    EDIT: #9004 has an alternative where we inform the model of the default
    shell itself.