Commit Graph

3828 Commits

  • chore: consolidate new() and initialize() for McpConnectionManager (#12255)
    ## Why
    `McpConnectionManager` used a two-phase setup (`new()` followed by
    `initialize()`), which forced call sites to construct placeholder state
    and then mutate it asynchronously. That made MCP startup/refresh flows
    harder to follow and easier to misuse, especially around cancellation
    token ownership.
    
    ## What changed
    - Replaced the two-phase initialization flow with a single async
    constructor: `McpConnectionManager::new(...) -> (Self,
    CancellationToken)`.
    - Added `McpConnectionManager::new_uninitialized()` for places that need
    an empty manager before async startup begins.
    - Added `McpConnectionManager::new_mcp_connection_manager_for_tests()`
    for test-only construction.
    - Updated MCP startup and refresh call sites in
    `codex-rs/core/src/codex.rs` to build a fresh manager via `new(...)`,
    swap it in, and update the startup cancellation token consistently.
    - Updated MCP snapshot/connector call sites in
    `codex-rs/core/src/mcp/mod.rs` and `codex-rs/core/src/connectors.rs` to
    use the consolidated constructor.
    - Removed the now-obsolete `reset_mcp_startup_cancellation_token()`
    helper in favor of explicit token replacement at the call sites.
    
    ## Testing
    - Not run (refactor-only change; no new behavior was intended).
  • Add configurable agent spawn depth (#12251)
    Summary
    - expose `agents.max_depth` in config schema and toml parsing, with
    defaults and validation
    - thread-spawn depth guards and multi-agent handler now respect the
    configured limit instead of a hardcoded value
    - ensure documentation and helpers account for agent depth limits
  • client side modelinfo overrides (#12101)
    TL;DR
    Add top-level `model_catalog_json` config support so users can supply a
    local model catalog override from a JSON file path (including adding new
    models) without backend changes.
    
    ### Problem
    Codex previously had no clean client-side way to replace/overlay model
    catalog data for local testing of model metadata and new model entries.
    
    ### Fix
    - Add top-level `model_catalog_json` config field (JSON file path).
    - Apply catalog entries when resolving `ModelInfo`:
      1. Base resolved model metadata (remote/fallback)
      2. Catalog overlay from `model_catalog_json`
    3. Existing global top-level overrides (`model_context_window`,
    `model_supports_reasoning_summaries`, etc.)
    
    ### Note
    Will revisit per-field overrides in a follow-up
    
    ### Tests
    Added tests
  • Move previous turn context tracking into ContextManager history (#12179)
    ## Summary
    - add `previous_context_item: Option<TurnContextItem>` to
    `ContextManager`
    - expose session/state accessors for reading and updating the stored
    previous context item
    - switch settings diffing to use `TurnContextItem` instead of
    `TurnContext`
    - remove submission-loop local `previous_context` and persist the
    previous context item in history
    
    ## Testing
    - `just fmt`
    - `just fix -p codex-core`
    - `cargo test -p codex-core --test all model_switching::`
    - `cargo test -p codex-core --test all collaboration_instructions::`
    - `cargo test -p codex-core --test all personality::`
    - `cargo test -p codex-core --test all
    permissions_messages::permissions_message_not_added_when_no_change`
  • Adjust MCP tool approval handling for custom servers (#11787)
    Summary
    This PR expands MCP client-side approval behavior beyond codex_apps and
    tightens elicitation capability signaling.
    
    - Removed the codex_apps-only gate in MCP tool approval checks, so
    local/custom MCP servers are now eligible for the same client-side
    approval prompt flow when tool annotations indicate side effects.
    - Updated approval memory keying to support tools without a connector ID
    (connector_id: Option<String>), allowing “Approve this Session” to be
    remembered even when connector metadata is missing.
    - Updated prompt text for non-codex_apps tools to identify origin as The
    <server> MCP server instead of This app.
    - Added MCP initialization capability policy so only codex_apps
    advertises MCP elicitation capability; other servers advertise no
    elicitation support.
    - Added regression tests for:
    server-specific prompt copy behavior
    codex-apps-only elicitation capability advertisement
    
    Testing
    - Not run (not requested)
  • feat: add configurable write_stdin timeout (#12228)
    Add max timeout as config for `write_stdin`. This is only used for empty
    `write_stdin`.
    
    Also increased the default value from 30s to 5mins.
  • docs: add codex security policy (#12193)
    ## Summary
    Adds SECURITY.MD with Codex security policy and Bugcrowd reporting
    guidance
  • feat: sub-agent injection (#12152)
    This PR adds parent-thread sub-agent completion notifications and change
    the prompt of the model to prevent if from being confused
  • Adjust memories rollout defaults (#12231)
    - Summary
    - raise `DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP` to 16 so more
    rollouts are allowed per startup
    - lower `DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS` to 6 to make rollouts
    eligible sooner
    - Testing
      - Not run (not requested)
  • Update docs links for feature flag notice (#12164)
    Summary
    - replace the stale `docs/config.md#feature-flags` reference in the
    legacy feature notice with the canonical published URL
    - align the deprecation notice test to expect the new link
    
    This addresses #12123
  • fix(linux-sandbox): mount /dev in bwrap sandbox (#12081)
    ## Summary
    - Updates the Linux bubblewrap sandbox args to mount a minimal `/dev`
    using `--dev /dev` instead of only binding `/dev/null`. tools needing
    entropy (git, crypto libs, etc.) can fail.
    
    - Changed mount order so `--dev /dev` is added before writable-root
    `--bind` mounts, preserving writable `/dev/*` submounts like `/dev/shm`
    
    ## Why
    Fixes sandboxed command failures when reading `/dev/urandom` (and
    similar standard device-node access).
    
    
    Fixes https://github.com/openai/codex/issues/12056
  • [apps] Update apps allowlist. (#12211)
    - [x] Update apps allowlist.
  • Stabilize app-server detached review and running-resume tests (#12203)
    ## Summary
    - stabilize
    `thread_resume_rejoins_running_thread_even_with_override_mismatch` by
    using a valid delayed second SSE response instead of an intentionally
    truncated stream
    - set `RUST_MIN_STACK=4194304` for spawned app-server test processes in
    `McpProcess` to avoid stack-sensitive CI overflows in detached review
    tests
    
    ## Why
    - the thread-resume assertion could race with a mocked stream-disconnect
    error and intermittently observe `systemError`
    - detached review startup is stack-sensitive in some CI environments;
    pinning a larger stack in the test harness removes that flake without
    changing product behavior
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server --test all
    suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch`
    - `cargo test -p codex-app-server --test all
    suite::v2::review::review_start_with_detached_delivery_returns_new_thread_id`
  • state: enforce 10 MiB log caps for thread and threadless process logs (#12038)
    ## Summary
    - enforce a 10 MiB cap per `thread_id` in state log storage
    - enforce a 10 MiB cap per `process_uuid` for threadless (`thread_id IS
    NULL`) logs
    - scope pruning to only keys affected by the current insert batch
    - add a cheap per-key `SUM(...)` precheck so windowed prune queries only
    run for keys that are currently over the cap
    - add SQLite indexes used by the pruning queries
    - add focused runtime tests covering both pruning behaviors
    
    ## Why
    This keeps log growth bounded by the intended partition semantics while
    preserving a small, readable implementation localized to the existing
    insert path.
    
    ## Local Latency Snapshot (No Truncation-Pressure Run)
    Collected from session `019c734f-1d16-7002-9e00-c966c9fbbcae` using
    local-only (uncommitted) instrumentation, while not specifically
    benchmarking the truncation-heavy regime.
    
    ### Percentiles By Query (ms)
    | query | count | p50 | p90 | p95 | p99 | max |
    |---|---:|---:|---:|---:|---:|---:|
    | `insert_logs.insert_batch` | 110 | 0.332 | 0.999 | 1.811 | 2.978 |
    3.493 |
    | `insert_logs.precheck.process` | 106 | 0.074 | 0.152 | 0.206 | 0.258 |
    0.426 |
    | `insert_logs.precheck.thread` | 73 | 0.118 | 0.206 | 0.253 | 1.025 |
    1.025 |
    | `insert_logs.prune.process` | 58 | 0.291 | 0.576 | 0.607 | 1.088 |
    1.088 |
    | `insert_logs.prune.thread` | 44 | 0.318 | 0.467 | 0.728 | 0.797 |
    0.797 |
    | `insert_logs.prune_total` | 110 | 0.488 | 0.976 | 1.237 | 1.593 |
    1.684 |
    | `insert_logs.total` | 110 | 1.315 | 2.889 | 3.623 | 5.739 | 5.961 |
    | `insert_logs.tx_begin` | 110 | 0.133 | 0.235 | 0.282 | 0.412 | 0.546 |
    | `insert_logs.tx_commit` | 110 | 0.259 | 0.689 | 0.772 | 1.065 | 1.080
    |
    
    ### `insert_logs.total` Histogram (ms)
    | bucket | count |
    |---|---:|
    | `<= 0.100` | 0 |
    | `<= 0.250` | 0 |
    | `<= 0.500` | 7 |
    | `<= 1.000` | 33 |
    | `<= 2.000` | 40 |
    | `<= 5.000` | 28 |
    | `<= 10.000` | 2 |
    | `<= 20.000` | 0 |
    | `<= 50.000` | 0 |
    | `<= 100.000` | 0 |
    | `> 100.000` | 0 |
    
    ## Local Latency Snapshot (Truncation-Heavy / Cap-Hit Regime)
    Collected from a run where cap-hit behavior was frequent (`135/180`
    insert calls), using local-only (uncommitted) instrumentation and a
    temporary local cap of `10_000` bytes for stress testing (not the merged
    `10 MiB` cap).
    
    ### Percentiles By Query (ms)
    | query | count | p50 | p90 | p95 | p99 | max |
    |---|---:|---:|---:|---:|---:|---:|
    | `insert_logs.insert_batch` | 180 | 0.524 | 1.645 | 2.163 | 3.424 |
    3.777 |
    | `insert_logs.precheck.process` | 171 | 0.086 | 0.235 | 0.373 | 0.758 |
    1.147 |
    | `insert_logs.precheck.thread` | 100 | 0.105 | 0.251 | 0.291 | 1.176 |
    1.622 |
    | `insert_logs.prune.process` | 109 | 0.386 | 0.839 | 1.146 | 1.548 |
    2.588 |
    | `insert_logs.prune.thread` | 56 | 0.253 | 0.550 | 1.148 | 2.484 |
    2.484 |
    | `insert_logs.prune_total` | 180 | 0.511 | 1.221 | 1.695 | 4.548 |
    5.512 |
    | `insert_logs.total` | 180 | 1.631 | 3.902 | 5.103 | 8.901 | 9.095 |
    | `insert_logs.total_cap_hit` | 135 | 1.876 | 4.501 | 5.547 | 8.902 |
    9.096 |
    | `insert_logs.total_no_cap_hit` | 45 | 0.520 | 1.700 | 2.079 | 3.294 |
    3.294 |
    | `insert_logs.tx_begin` | 180 | 0.109 | 0.253 | 0.287 | 1.088 | 1.406 |
    | `insert_logs.tx_commit` | 180 | 0.267 | 0.813 | 1.170 | 2.497 | 2.574
    |
    
    ### `insert_logs.total` Histogram (ms)
    | bucket | count |
    |---|---:|
    | `<= 0.100` | 0 |
    | `<= 0.250` | 0 |
    | `<= 0.500` | 16 |
    | `<= 1.000` | 39 |
    | `<= 2.000` | 60 |
    | `<= 5.000` | 54 |
    | `<= 10.000` | 11 |
    | `<= 20.000` | 0 |
    | `<= 50.000` | 0 |
    | `<= 100.000` | 0 |
    | `> 100.000` | 0 |
    
    ### `insert_logs.total` Histogram When Cap Was Hit (ms)
    | bucket | count |
    |---|---:|
    | `<= 0.100` | 0 |
    | `<= 0.250` | 0 |
    | `<= 0.500` | 0 |
    | `<= 1.000` | 22 |
    | `<= 2.000` | 51 |
    | `<= 5.000` | 51 |
    | `<= 10.000` | 11 |
    | `<= 20.000` | 0 |
    | `<= 50.000` | 0 |
    | `<= 100.000` | 0 |
    | `> 100.000` | 0 |
    
    ### Performance Takeaways
    - Even in a cap-hit-heavy run (`75%` cap-hit calls), `insert_logs.total`
    stays sub-10ms at p99 (`8.901ms`) and max (`9.095ms`).
    - Calls that did **not** hit the cap are materially cheaper
    (`insert_logs.total_no_cap_hit` p95 `2.079ms`) than cap-hit calls
    (`insert_logs.total_cap_hit` p95 `5.547ms`).
    - Compared to the earlier non-truncation-pressure run, overall
    `insert_logs.total` rose from p95 `3.623ms` to p95 `5.103ms`
    (+`1.48ms`), indicating bounded overhead when pruning is active.
    - This truncation-heavy run used an intentionally low local cap for
    stress testing; with the real 10 MiB cap, cap-hit frequency should be
    much lower in normal sessions.
    
    ## Testing
    - `just fmt` (in `codex-rs`)
    - `cargo test -p codex-state` (in `codex-rs`)
  • app-server: expose loaded thread status via read/list and notifications (#11786)
    Motivation
    - Today, a newly connected client has no direct way to determine the
    current runtime status of threads from read/list responses alone.
    - This forces clients to infer state from transient events, which can
    lead to stale or inconsistent UI when reconnecting or attaching late.
    
    Changes
    - Add `status` to `thread/read` responses.
    - Add `statuses` to `thread/list` responses.
    - Emit `thread/status/changed` notifications with `threadId` and the new
    status.
    - Track runtime status for all loaded threads and default unknown
    threads to `idle`.
    - Update protocol/docs/tests/schema fixtures for the revised API.
    
    Testing
    - Validated protocol API changes with automated protocol tests and
    regenerated schema/type fixtures.
    - Validated app-server behavior with unit and integration test suites,
    including status transitions and notifications.
  • [apps] Temporary app block. (#12180)
    - [x] Temporary app block.
  • fix: Remove citation (#12187)
    Remove citation requirement until we figure out a better visualization
  • app-server support for Windows sandbox setup. (#12025)
    app-server support for initiating Windows sandbox setup.
    server responds quickly to setup request and makes a future RPC call
    back to client when the setup finishes.
    
    The TUI implementation is unaffected but in a future PR I'll update the
    TUI to use the shared setup helper
    (`windows_sandbox.run_windows_sandbox_setup`)
  • js_repl: canonicalize paths for node_modules boundary checks (#12177)
    ## Summary
    
    Fix `js_repl` package-resolution boundary checks for macOS temp
    directory path aliasing (`/var` vs `/private/var`).
    
    ## Problem
    
    `js_repl` verifies that resolved bare-package imports stay inside a
    configured `node_modules` root.
    On macOS, temp directories are commonly exposed as `/var/...` but
    canonicalize to `/private/var/...`.
    Because the boundary check compared raw paths with `path.relative(...)`,
    valid resolutions under temp dirs could be misclassified as escaping the
    allowed base, causing false `Module not found` errors.
    
    ## Changes
    
    - Add `fs` import in the JS kernel.
    - Add `canonicalizePath()` using `fs.realpathSync.native(...)` (with
    safe fallback).
    - Canonicalize both `base` and `resolvedPath` before running the
    `node_modules` containment check.
    
    ## Impact
    
    - Fixes false-negative boundary checks for valid package resolutions in
    macOS temp-dir scenarios.
    - Keeps the existing security boundary behavior intact.
    - Scope is limited to `js_repl` kernel module path validation logic.
    
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    - 👉 `1` https://github.com/openai/codex/pull/12177
    -  `2` https://github.com/openai/codex/pull/10673
  • memories: bump rollout summary slug cap to 60 (#12167)
    ## Summary
    Increase the rollout summary filename slug cap from 20 to 60 characters
    in memory storage.
    
    ## What changed
    - Updated `ROLLOUT_SLUG_MAX_LEN` from `20` to `60` in:
      - `codex-rs/core/src/memories/storage.rs`
    - Updated slug truncation test to verify 60-char behavior.
    
    ## Why
    This preserves more semantic context in rollout summary filenames while
    keeping existing normalization behavior unchanged.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-core
    memories::storage::tests::rollout_summary_file_stem_sanitizes_and_truncates_slug
    -- --exact`
  • fix: file watcher (#12105)
    The issue was that the file_watcher never unsubscribe a file watch. All
    of them leave in the owning of the ThreadManager. As a result, for each
    newly created thread we create a new file watcher but this one never get
    deleted even if we close the thread. On Unix system, a file watcher uses
    an `inotify` and after some time we end up having consumed all of them.
    
    This PR adds a mechanism to unsubscribe a file watcher when a thread is
    dropped
  • Fixed a hole in token refresh logic for app server (#11802)
    We've continued to receive reports from users that they're seeing the
    error message "Your access token could not be refreshed because your
    refresh token was already used. Please log out and sign in again." This
    PR fixes two holes in the token refresh logic that lead to this
    condition.
    
    Background: A previous change in token refresh introduced the
    `UnauthorizedRecovery` object. It implements a state machine in the core
    agent loop that first performs a load of the on-disk auth information
    guarded by a check for matching account ID. If it finds that the on-disk
    version has been updated by another instance of codex, it uses the
    reloaded auth tokens. If the on-disk version hasn't been updated, it
    issues a refresh request from the token authority.
    
    There are two problems that this PR addresses:
    
    Problem 1: We weren't doing the same thing for the code path used by the
    app server interface. This PR effectively replicates the
    `UnauthorizedRecovery` logic for that code path.
    
    Problem 2: The `UnauthorizedRecovery` logic contained a hole in the
    `ReloadOutcome::Skipped` case. Here's the scenario. A user starts two
    instances of the CLI. Instance 1 is active (working on a task), instance
    2 is idle. Both instances have the same in-memory cached tokens. The
    user then runs `codex logout` or `codex login` to log in to a separate
    account, which overwrites the `auth.json` file. Instance 1 receives a
    401 and refreshes its token, but it doesn't write the new token to the
    `auth.json` file because the account ID doesn't match. Instance 2 is
    later activated and presented with a new task. It immediately hits a 401
    and attempts to refresh its token but fails because its cached refresh
    token is now invalid. To avoid this situation, I've changed the logic to
    immediately fail a token refresh if the user has since logged out or
    logged in to another account. This will still be seen as an error by the
    user, but the cause will be clearer.
    
    I also took this opportunity to clean up the names of existing functions
    to make their roles clearer.
    * `try_refresh_token` is renamed `request_chatgpt_token_refresh`
    * the existing `refresh_token` is renamed `refresh_token_from_authority`
    (there's a new higher-level function named `refresh_token` now)
    * `refresh_tokens` is renamed `refresh_and_persist_chatgpt_token`, and
    it now implicitly reloads
    * `update_tokens` is renamed `persist_tokens`
  • Disable collab tools during review delegation (#12157)
    Summary
    - prevent delegated review agents from re-enabling blocked tools by
    explicitly disabling the Collab feature alongside web search and view
    image controls
    
    Testing
    - Not run (not requested)
  • Stop filtering model tools in js_repl_tools_only mode (#12069)
    ## Summary
    This change removes tool-list filtering in `js_repl_tools_only` mode and
    relies on the normal model tool descriptions, while still enforcing that
    tool execution must go through `js_repl` + `codex.tool(...)`.
    
    ## Motivation
    The previous `js_repl_tools_only` filtering hid most tools from the
    model request, which diverged from standard tool-list behavior and made
    signatures less discoverable. I tested that this filtering is not
    needed, and the model can follow the prompt to only call tools via
    `js_repl`.
    
    ## What Changed
    - `filter_tools_for_model(...)` in `core/src/tools/spec.rs` is now a
    pass-through (no filtering when `js_repl_tools_only` is enabled).
    - Updated tests to assert that model tools are not filtered in
    `js_repl_tools_only` mode.
    - Updated dynamic-tool test to assert dynamic tools remain visible in
    model tool specs.
    - Removed obsolete test helper used only by the old filtering
    assertions.
    
    ## Safety / Behavior
    - This commit does **not** relax execution policy.
    - Direct model tool calls remain blocked in `js_repl_tools_only` mode
    (except internal `js_repl` tools), and callers are instructed to use
    `js_repl` + `codex.tool(...)`.
    
    ## Testing
    - `cargo test -p codex-core js_repl_tools_only`
    - Manual rollout validation showed the model can follow the `js_repl`
    routing instructions without needing filtered tool lists.
    
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    - 👉 `1` https://github.com/openai/codex/pull/12069
    -  `2` https://github.com/openai/codex/pull/10673
    -  `3` https://github.com/openai/codex/pull/10670
  • Enable default status line indicators in TUI config (#12015)
    Default statusline to something
    <img width="307" height="83" alt="Screenshot 2026-02-17 at 18 16 12"
    src="https://github.com/user-attachments/assets/44e16153-0aa2-4c1a-9b4a-02e2feb8b7f6"
    />
  • fix: Restricted Read: /System is too permissive for macOS platform de… (#11798)
    …fault
    
    Update the list of platform defaults included for `ReadOnlyAccess`.
    
    When `ReadOnlyAccess::Restricted::include_platform_defaults` is `true`,
    the policy defined in
    `codex-rs/core/src/seatbelt_platform_defaults.sbpl` is appended to
    enable macOS programs to function properly.
  • [js_repl] paths for node module resolution can be specified for js_repl (#11944)
    # External (non-OpenAI) Pull Request Requirements
    
    In `js_repl` mode, module resolution currently starts from
    `js_repl_kernel.js`, which is written to a per-kernel temp dir. This
    effectively means that bare imports will not resolve.
    
    This PR adds a new config option, `js_repl_node_module_dirs`, which is a
    list of dirs that are used (in order) to resolve a bare import. If none
    of those work, the current working directory of the thread is used.
    
    For example:
    ```toml
    js_repl_node_module_dirs = [
        "/path/to/node_modules/",
        "/other/path/to/node_modules/",
    ]
    ```
  • Add model-visible context layout snapshot tests (#12073)
    ## Summary
    - add a dedicated `core/tests/suite/model_visible_layout.rs` snapshot
    suite to materialize model-visible request layout in high-value
    scenarios
    - add three reviewer-focused snapshot scenarios:
      - turn-level context updates (cwd / permissions / personality)
      - first post-resume turn with model hydration + personality change
    - first post-resume turn where pre-turn model override matches rollout
    model
    - wire the new suite into `core/tests/suite/mod.rs`
    - commit generated `insta` snapshots under `core/tests/suite/snapshots/`
    
    ## Why
    This creates a stable, reviewable baseline of model-visible context
    layout against `main` before follow-on context-management refactors. It
    lets subsequent PRs show focused snapshot diffs for behavior changes
    instead of introducing the test surface and behavior changes at once.
    
    ## Testing
    - `just fmt`
    - `INSTA_UPDATE=always cargo test -p codex-core model_visible_layout`
  • codex-api: realtime websocket session.create + typed inbound events (#12036)
    ## Summary
    - add realtime websocket client transport in codex-api
    - send session.create on connect with backend prompt and optional
    conversation_id
    - keep session.update for prompt changes after connect
    - switch inbound event parsing to a tagged enum (typed variants instead
    of optional field bag)
    - add a websocket e2e integration test in
    codex-rs/codex-api/tests/realtime_websocket_e2e.rs
    
    ## Why
    This moves the realtime transport to an explicit session-create
    handshake and improves protocol safety with typed inbound events.
    
    ## Testing
    - Added e2e integration test coverage for session create + event flow in
    the API crate.
  • feat(core): zsh exec bridge (#12052)
    zsh fork PR stack:
    - https://github.com/openai/codex/pull/12051 
    - https://github.com/openai/codex/pull/12052 👈 
    
    ### Summary
    This PR introduces a feature-gated native shell runtime path that routes
    shell execution through a patched zsh exec bridge, removing MCP-specific
    behavior from the shell hot path while preserving existing
    CommandExecution lifecycle semantics.
    
    When shell_zsh_fork is enabled, shell commands run via patched zsh with
    per-`execve` interception through EXEC_WRAPPER. Core receives wrapper
    IPC requests over a Unix socket, applies existing approval policy, and
    returns allow/deny before the subcommand executes.
    
    ### What’s included
    **1) New zsh exec bridge runtime in core**
    - Wrapper-mode entrypoint (maybe_run_zsh_exec_wrapper_mode) for
    EXEC_WRAPPER invocations.
    - Per-execution Unix-socket IPC handling for wrapper requests/responses.
    - Approval callback integration using existing core approval
    orchestration.
    - Streaming stdout/stderr deltas to existing command output event
    pipeline.
    - Error handling for malformed IPC, denial/abort, and execution
    failures.
    
    **2) Session lifecycle integration**
    SessionServices now owns a `ZshExecBridge`.
    Session startup initializes bridge state; shutdown tears it down
    cleanly.
    
    **3) Shell runtime routing (feature-gated)**
    When `shell_zsh_fork` is enabled:
    - Build execution env/spec as usual.
    - Add wrapper socket env wiring.
    - Execute via `zsh_exec_bridge.execute_shell_request(...)` instead of
    the regular shell path.
    - Non-zsh-fork behavior remains unchanged.
    
    **4) Config + feature wiring**
    - Added `Feature::ShellZshFork` (under development).
    - Added config support for `zsh_path` (optional absolute path to patched
    zsh):
    - `Config`, `ConfigToml`, `ConfigProfile`, overrides, and schema.
    - Session startup validates that `zsh_path` exists/usable when zsh-fork
    is enabled.
    - Added startup test for missing `zsh_path` failure mode.
    
    **5) Seatbelt/sandbox updates for wrapper IPC**
    - Extended seatbelt policy generation to optionally allow outbound
    connection to explicitly permitted Unix sockets.
    - Wired sandboxing path to pass wrapper socket path through to seatbelt
    policy generation.
    - Added/updated seatbelt tests for explicit socket allow rule and
    argument emission.
    
    **6) Runtime entrypoint hooks**
    - This allows the same binary to act as the zsh wrapper subprocess when
    invoked via `EXEC_WRAPPER`.
    
    **7) Tool selection behavior**
    - ToolsConfig now prefers ShellCommand type when shell_zsh_fork is
    enabled.
    - Added test coverage for precedence with unified-exec enabled.
  • Unify remote compaction snapshot mocks around default endpoint behavior (#12050)
    ## Summary
    - standardize remote compaction test mocking around one default behavior
    in shared helpers
    - make default remote compact mocks mirror production shape: keep
    `message/user` + `message/developer`, drop assistant/tool artifacts,
    then append a summary user message
    - switch non-special `compact_remote` tests to the shared default mock
    instead of ad-hoc JSON payloads
    
    ## Special-case tests that still use explicit mocks
    - remote compaction error payload / HTTP failure behavior
    - summary-only compact output behavior
    - manual `/compact` with no prior user messages
    - stale developer-instruction injection coverage
    
    ## Why
    This removes inconsistent manual remote compaction fixtures and gives us
    one source of truth for normal remote compact behavior, while preserving
    explicit mocks only where tests intentionally cover non-default
    behavior.
  • feat(core): plumb distinct approval ids for command approvals (#12051)
    zsh fork PR stack:
    - https://github.com/openai/codex/pull/12051 👈 
    - https://github.com/openai/codex/pull/12052
    
    With upcoming support for a fork of zsh that allows us to intercept
    `execve` and run execpolicy checks for each subcommand as part of a
    `CommandExecution`, it will be possible for there to be multiple
    approval requests for a shell command like `/path/to/zsh -lc 'git status
    && rg \"TODO\" src && make test'`.
    
    To support that, this PR introduces a new `approval_id` field across
    core, protocol, and app-server so that we can associate approvals
    properly for subcommands.
  • Chore: remove response model check and rely on header model for downgrade (#12061)
    ### Summary
    Ensure that we use the model value from the response header only so that
    we are guaranteed with the correct slug name. We are no longer checking
    against the model value from response so that we are less likely to have
    false positive.
    
    There are two different treatments - for SSE we use the header from the
    response and for websocket we check top-level events.
  • app-server: Emit thread archive/unarchive notifications (#12030)
    * Add v2 server notifications `thread/archived` and `thread/unarchived`
    with a `threadId` payload.
    * Wire new events into `thread/archive` and `thread/unarchive` success
    paths.
    * Update app-server protocol/schema/docs accordingly.
    
    Testing:
    - Updated archive/unarchive end-to-end tests to verify both
    notifications are emitted with the expected thread id payload.
  • tui: exit session on Ctrl+C in cwd change prompt (#12040)
    ## Summary
    - change the cwd-change prompt (shown when resuming/forking across
    different directories) so `Ctrl+C`/`Ctrl+D` exits the session instead of
    implicitly selecting "Use session directory"
    - introduce explicit prompt and resolver exit outcomes so this intent is
    propagated cleanly through both startup resume/fork and in-app `/resume`
    flows
    - add a unit test that verifies `Ctrl+C` exits rather than selecting an
    option
    
    ## Why
    Previously, pressing `Ctrl+C` on this prompt silently picked one of the
    options, which made it hard to abort. This aligns the prompt with the
    expected quit behavior.
    
    ## Codex author
    `codex resume 019c6d39-bbfb-7dc3-8008-1388a054e86d`