Commit Graph

6495 Commits

  • Add unsigned macOS release artifacts (#22559)
    ## Summary
    - Upload unsigned macOS release binaries before signing so they remain
    available from the workflow run if signing fails
    - Add a manual `workflow_dispatch` option, `sign_macos`, defaulting to
    `true`
    - When `sign_macos=false`, skip macOS signing, signed-name macOS
    artifacts, DMGs, npm/DotSlash/PyPI publishing, latest release marking,
    and `latest-alpha-cli` updates
    
    
    ## Process
    HAVE NOT TESTED YET BUT we should be able to run
    ```
    gh workflow run rust-release.yml \
      -R openai/codex \
      --ref rust-v0.132.0 \
      -f sign_macos=false
    ```
    
    which will then start the rust-release script with `sign_macos` and
    therefore do not codesign mac and also no release afterward.
  • [codex] Canonicalize shared workspace plugin IDs (#22564)
    ## Summary
    - Canonicalize private and unlisted workspace shared plugin IDs to
    `workspace-shared-with-me`.
    - Keep `plugin/list` private/unlisted shared-with-me buckets as UI
    grouping only.
    - Update share read/list/checkout and cache cleanup coverage for the
    canonical namespace.
    
    ## Tests
    - `cargo test -p codex-app-server --test all
    plugin_list_fetches_shared_with_me_kind`
    - `cargo test -p codex-app-server --test all
    plugin_read_returns_share_context_for_shared_remote_plugin`
    - `cargo test -p codex-app-server --test all suite::v2::plugin_share`
    - `cargo test -p codex-core-plugins
    list_remote_plugin_shares_fetches_created_workspace_plugins`
    - `cargo test -p codex-core-plugins
    stale_remote_plugin_cleanup_removes_old_shared_with_me_cache_and_keeps_canonical_cache`
    - `git diff --check`
  • Refactor chatwidget orchestration into modules (phase 5) (#22537)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow, #22433 completed phase 3 by extracting protocol,
    replay, streaming, and tool lifecycle handling, and #22518 completed
    phase 4 by extracting settings, popups, and status surfaces.
    
    This PR is phase 5. It cleans up the remaining constructor and
    orchestration code now that the larger behavior domains have moved out,
    leaving `chatwidget.rs` much closer to the composition layer the cleanup
    was aiming for. This is once again a mechanical movement of existing
    functions. No functional changes.
    
    ## What Changed
    
    - Added focused modules for widget construction and initial wiring,
    session configuration flow, key/composer interaction routing, review
    popup orchestration, desktop notification coalescing, and render
    composition.
    - Moved the remaining constructor, session setup, interaction,
    notification, review picker, and rendering helpers out of
    `codex-rs/tui/src/chatwidget.rs`.
    - Preserved the existing startup/session behavior, keyboard handling,
    review picker flow, notification priority behavior, and render
    composition while shrinking the central widget module substantially.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    composition surface for the extracted behavior modules.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    Completed in #22433.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers. Completed in #22518.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer. This PR.
    
    ## Verification
    
    - `cargo check -p codex-tui`
    - `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
    - `cargo test -p codex-tui chatwidget::tests::plan_mode`
    - `cargo test -p codex-tui chatwidget::tests::review_mode`
    - `cargo test -p codex-tui chatwidget::tests::status_and_layout`
    
    `cargo test -p codex-tui` also compiles and begins running, but aborts
    in the unchanged app-side test
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
    with the same reproducible stack overflow noted in phase 4.
  • Remove resurrected /collab slash command (#22535)
    ## Summary
    `/collab` was intentionally removed in
    [#12012](https://github.com/openai/codex/pull/12012), but the
    TUI/app-server migration accidentally brought that slash-command path
    back. This restores the earlier product decision so the TUI no longer
    advertises or dispatches `/collab`. This command was redundant because
    it did the same thing as `/plan` but in a less-intuitive way.
    
    ## What Changed
    - Remove `SlashCommand::Collab` from the TUI slash-command surface.
    - Delete the picker and app-event plumbing that only existed to service
    `/collab`.
    - Remove obsolete TUI test coverage for the deleted picker flow.
  • Spill oversized PreToolUse additionalContext (#22529)
    # Why
    
    `PreToolUse.additionalContext` became model-visible after #20692, but
    the hook-output spilling path from #21069 never picked up that newer
    lane. As a result, oversized `PreToolUse` context could bypass the
    truncation/spill treatment that already applies to the other hook
    outputs Codex forwards to the model.
    
    # What
    
    - Run `PreToolUseOutcome.additional_contexts` through
    `maybe_spill_texts(...)`
    - Add an integration test proving a large `PreToolUse.additionalContext`
    is replaced with a truncated preview plus spill-file pointer, while the
    full text is preserved on disk.
  • Make multi_agent_v2 wait_agent timeouts configurable (#22528)
    ## Why
    
    `multi_agent_v2` already allowed configuring the minimum `wait_agent`
    timeout, but the default timeout and upper bound were still hard-coded.
    That made it hard to tune waits for subagent mailbox activity in
    sessions that need either faster wakeups or longer waits, and it meant
    the model-visible `wait_agent` schema could not fully reflect the
    resolved runtime limits.
    
    ## What Changed
    
    - Added `features.multi_agent_v2.max_wait_timeout_ms` and
    `features.multi_agent_v2.default_wait_timeout_ms` alongside the existing
    `min_wait_timeout_ms` setting.
    - Validated all three timeouts in config as `0..=3_600_000`, with
    `min_wait_timeout_ms <= default_wait_timeout_ms <= max_wait_timeout_ms`.
    - Thread and review session tool config now passes the resolved
    min/default/max values into the `wait_agent` tool schema.
    - `wait_agent` now uses the configured default when `timeout_ms` is
    omitted and rejects explicit values outside the configured min/max range
    instead of silently clamping them.
    - Updated the generated config schema and config-lock test coverage for
    the new fields.
  • Avoid PowerShell profiles in elevated Windows sandbox (#21400)
    ## Why
    
    On Windows, elevated sandboxed commands run under a dedicated sandbox
    account while `HOME` / `USERPROFILE` can still point at the real user's
    profile directory. For PowerShell login shells, that combination can
    make the sandbox account try to load the real user's PowerShell profile
    script. If the sandbox account's execution policy differs from the real
    user's policy, startup can emit profile-loading errors before the
    requested command runs.
    
    For this backend, loading the profile is not a faithful user login
    shell: it is cross-account profile execution. Treating these PowerShell
    invocations as non-login shells avoids that invalid startup path.
    
    ## Why This Happens Late
    
    The normal `login` decision is resolved when shell argv is created, but
    that point is too early to make this Windows sandbox-specific decision.
    At argv creation time we do not yet know the actual sandbox attempt that
    will run the command. A turn can include sandboxed and unsandboxed
    attempts, and a broad turn-level override would also affect Full Access
    commands where the user's profile should remain available.
    
    Instead, this change carries the selected `ShellType` alongside the argv
    and applies the `-NoProfile` adjustment in the shell runtimes once the
    `SandboxAttempt` is known. That keeps the override scoped to actual
    `WindowsRestrictedToken` attempts with `WindowsSandboxLevel::Elevated`.
    
    The runtime uses the selected shell metadata rather than re-detecting
    PowerShell from argv. That avoids brittle parsing and covers PowerShell
    invocation shapes such as `-EncodedCommand`.
    
    ## What Changed
    
    - Carry selected shell metadata through `exec_command` / unified exec
    requests and shell tool requests.
    - Insert `-NoProfile` for PowerShell commands only when the runtime is
    about to execute a sandboxed elevated Windows attempt.
    - Add focused unit coverage for elevated Windows PowerShell,
    `-EncodedCommand`, existing `-NoProfile`, legacy restricted-token
    attempts, unsandboxed attempts, and non-PowerShell commands.
    
    ## Verification
    
    - `cargo test -p codex-core disable_powershell_profile_tests`
    - `cargo test -p codex-core test_get_command`
    - `cargo clippy --fix --tests --allow-dirty --allow-no-vcs -p
    codex-core`
    
    A full `cargo test -p codex-core` run was also attempted during
    development, but it still hit an unrelated stack overflow in
    `agent::control` tests before reaching this area.
  • clean up instructions (#22543)
    rm behavioral steering in tool docs for code mode.
  • feat(cli): add codex doctor diagnostics (#22336)
    ## Why
    
    Users and support need a single command that captures the local Codex
    runtime, configuration, auth, terminal, network, and state shape without
    asking the user to know which diagnostic depth to choose first. `codex
    doctor` now runs the useful checks by default and makes the detailed
    human output the default because the command is usually run when someone
    already needs context.
    
    The command also targets concrete support failure modes we have seen
    while iterating on the design:
    
    - update-target mismatches like #21956, where the installed package
    manager target can differ from the running executable
    - terminal and multiplexer issues that depend on `TERM`, tmux/zellij
    state, color handling, and TTY metadata
    - provider-specific HTTP/WebSocket connectivity, including ChatGPT
    WebSocket handshakes and API-key/provider endpoint reachability
    - local state/log SQLite integrity problems and large rollout
    directories
    - feedback reports that need an attached, redacted diagnostic snapshot
    without asking the user to run a second command
    
    ## What Changed
    
    - Adds `codex doctor` as a grouped CLI diagnostic report with default
    detailed output and `--summary` for the compact view.
    - Adds stable report sections for Environment, Configuration, Updates,
    Connectivity, and Background Server, plus a top Notes block that
    promotes anomalies such as available updates, large rollout directories,
    optional MCP issues, and mixed auth signals.
    - Adds runtime provenance, install consistency, bundled/system search
    readiness, terminal/multiplexer metadata, `config.toml` parse status,
    auth mode details, sandbox details, feature flag summaries, update
    cache/latest-version state, app-server daemon state, SQLite integrity
    checks, rollout statistics, and provider-aware network diagnostics.
    - Adds ChatGPT WebSocket diagnostics that report the negotiated HTTP
    upgrade as `HTTP 101 Switching Protocols` and include timeout, DNS,
    auth, and provider context in detailed output.
    - Makes reachability provider-aware: API-key OpenAI setups check the API
    endpoint, ChatGPT auth checks the ChatGPT path, and custom/AWS/local
    providers check configured HTTP endpoints when available.
    - Adds structured, redacted JSON output where `checks` is keyed by check
    id and `details` is a key/value object for support tooling.
    - Integrates doctor with feedback uploads by attaching a best-effort
    `codex-doctor-report.json` report and adding derived Sentry tags for
    overall status and failing/warning checks.
    - Updates the TUI feedback consent copy so users can see that the doctor
    report is included when logs/diagnostics are uploaded.
    - Updates the CLI bug issue template to ask reporters for `codex doctor
    --json` and render pasted reports as JSON.
    
    ## Example Output
    
    The examples below are sanitized from local smoke runs with `--no-color`
    so the structure is reviewable in plain text.
    
    ### `codex doctor`
    
    ```text
    Codex Doctor v0.0.0 · macos-aarch64
    
    Notes
       ↑ updates      0.130.0 available (current 0.0.0, dismissed 0.128.0)
       ⚠ rollouts     1,526 active files · 2.53 GB on disk
       ⚠ mcp          MCP configuration has optional issues
       ⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
    ─────────────────────────────────────────────────────────────
    
    Environment
      ✓ runtime      local debug build
          version                  0.0.0
          install method           other
          commit                   unknown
          executable               ~/code/codex.fcoury-doct…x-rs/target/debug/codex
      ✓ install      consistent
          context                  other
          managed by               npm: no · bun: no · package root —
          PATH entries (2)         ~/.local/share/mise/installs/node/24/bin/codex
                                   ~/.local/share/mise/shims/codex
      ✓ search       ripgrep 15.1.0 (system, `rg`)
      ✓ terminal     Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
          terminal                 Ghostty
          TERM_PROGRAM             ghostty
          terminal version         1.3.2-main-+b0f827665
          TERM                     xterm-256color
          multiplexer              tmux 3.6a
          tmux extended-keys       on
          tmux allow-passthrough   on
          tmux set-clipboard       on
      ✓ state        databases healthy
          CODEX_HOME               ~/.codex (dir)
          state DB                 ~/.codex/state_5.sqlite (file) · integrity ok
          log DB                   ~/.codex/logs_2.sqlite (file) · integrity ok
          active rollouts          1,526 files · 2.53 GB (avg 1.70 MB)
          archived rollouts        8 files · 3.84 MB (avg 491.11 KB)
    
    Configuration
      ✓ config       loaded
          model                    gpt-5.5 · openai
          cwd                      ~/code/codex.fcoury-doctor/codex-rs
          config.toml              ~/.codex/config.toml
          config.toml parse        ok
          MCP servers              1
          feature flags            36 enabled · 7 overridden (full list with --all)
          overrides                code_mode, code_mode_only, memories, chronicle, goals, remote_control, prevent_idle_sleep
      ✓ auth         auth is configured
          auth storage mode        File
          auth file                ~/.codex/auth.json
          auth env vars present    OPENAI_API_KEY
          stored auth mode         chatgpt
          stored API key           false
          stored ChatGPT tokens    true
          stored agent identity    false
      ⚠ mcp          MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
          configured servers       1
          disabled servers         0
          streamable_http servers  1
          optional reachability    openaiDeveloperDocs: https://developers.openai.com/mcp (HEAD connect failed; GET connect failed)
      ✓ sandbox      restricted fs + restricted network · approval OnRequest
          approval policy          OnRequest
          filesystem sandbox       restricted
          network sandbox          restricted
    
    Connectivity
      ✓ network      network-related environment looks readable
      ✓ websocket    connected (HTTP 101 Switching Protocols) · 15s timeout
          model provider           openai
          provider name            OpenAI
          wire API                 responses
          supports websockets      true
          connect timeout          15000 ms
          auth mode                chatgpt
          endpoint                 wss://chatgpt.com/backend-api/<redacted>
          DNS                      2 IPv4, 2 IPv6, first IPv6
          handshake result         HTTP 101 Switching Protocols
      ✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
          reachability mode        API key auth
          openai API               https://api.openai.com/v1 connect failed (required)
    
    Background Server
      ○ app-server   not running (ephemeral mode)
    
    ─────────────────────────────────────────────────────────────
    11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
    
    --summary compact output           --all expand truncated lists
    --json redacted report
    ```
    
    ### `codex doctor --summary`
    
    ```text
    Codex Doctor v0.0.0 · macos-aarch64
    
    Notes
       ↑ updates      0.130.0 available (current 0.0.0, dismissed 0.128.0)
       ⚠ rollouts     1,526 active files · 2.53 GB on disk
       ⚠ mcp          MCP configuration has optional issues
       ⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
    ─────────────────────────────────────────────────────────────
    
    Environment
      ✓ runtime      local debug build
      ✓ install      consistent
      ✓ search       ripgrep 15.1.0 (system, `rg`)
      ✓ terminal     Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
      ✓ state        databases healthy
    
    Configuration
      ✓ config       loaded
      ✓ auth         auth is configured
      ⚠ mcp          MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
      ✓ sandbox      restricted fs + restricted network · approval OnRequest
    
    Updates
      ✓ updates      update configuration is locally consistent
    
    Connectivity
      ✓ network      network-related environment looks readable
      ✓ websocket    connected (HTTP 101 Switching Protocols) · 15s timeout
      ✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
    
    Background Server
      ○ app-server   not running (ephemeral mode)
    
    ─────────────────────────────────────────────────────────────
    11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
    
    Run codex doctor without --summary for detailed diagnostics.
    --all expand truncated lists       --json redacted report
    ```
    
    ### `codex doctor --json` shape
    
    ```json
    {
      "schema_version": 1,
      "overall_status": "fail",
      "checks": {
        "runtime.provenance": {
          "id": "runtime.provenance",
          "category": "Environment",
          "status": "ok",
          "summary": "local debug build",
          "details": {
            "version": "0.0.0",
            "install method": "other",
            "commit": "unknown"
          }
        },
        "sandbox.helpers": {
          "id": "sandbox.helpers",
          "category": "Configuration",
          "status": "ok",
          "summary": "restricted fs + restricted network · approval OnRequest",
          "details": {
            "approval policy": "OnRequest",
            "filesystem sandbox": "restricted",
            "network sandbox": "restricted"
          }
        }
      }
    }
    ```
    
    ### `/feedback` new sentry attachment
    
    <img width="938" height="798" alt="CleanShot 2026-05-13 at 15 36 14"
    src="https://github.com/user-attachments/assets/715e62e0-d7b4-4fea-a35a-fd5d5d33c4c0"
    />
    
    ### New section in CLI issue template
    
    <img width="1164" height="435" alt="CleanShot 2026-05-13 at 15 47 24"
    src="https://github.com/user-attachments/assets/9081dc25-a28c-4afa-8ba1-e299c2b4031d"
    />
    
    ## How to Test
    
    1. Run `cargo run --bin codex -- doctor --no-color`.
    2. Confirm the detailed report is the default and includes promoted
    Notes, grouped sections, terminal details, state DB integrity, rollout
    stats, provider reachability, WebSocket diagnostics, and app-server
    status.
    3. Run `cargo run --bin codex -- doctor --summary --no-color`.
    4. Confirm the compact view keeps the same sections and summary counts
    but omits detailed key/value rows.
    5. Run `cargo run --bin codex -- doctor --json`.
    6. Confirm the output is redacted JSON, `checks` is an object keyed by
    check id, and each check's `details` is a key/value object.
    7. Preview the CLI bug issue template and confirm the `Codex doctor
    report` field appears after the terminal field, asks for `codex doctor
    --json`, and renders pasted output as JSON.
    8. Start a feedback flow that includes logs.
    9. Confirm the upload consent copy lists `codex-doctor-report.json`
    alongside the log attachments.
    
    Targeted tests:
    
    - `cargo test -p codex-cli doctor`
    - `cargo test -p codex-app-server
    doctor_report_tags_summarize_status_counts`
    - `cargo test -p codex-feedback`
    - `cargo test -p codex-tui feedback_view`
    - `just argument-comment-lint`
    - `git diff --check`
  • [codex] Fix TUI wrapping for external borrowed slices (#21235)
    Fixes #20587, reported by @noeljackson.
    
    This prevents the TUI wrapping code from panicking when `textwrap`
    returns a borrowed slice that does not point into the original source
    text. The fix follows the direction proposed by @misrtjakub in the issue
    comment: validate the borrowed slice pointer range first, and fall back
    to the existing owned-line mapper when the slice is external.
    
    - Guards borrowed wrapped slices before converting pointer offsets into
    byte ranges.
    - Reuses the existing owned-line range recovery path for external
    borrowed slices.
    - Adds coverage for rejecting borrowed slices outside the source text.
    
    End-user testing steps:
    - Start Codex in TUI mode under a PTY wrapper that can inject stdin
    after startup.
    - Inject `\x1b[200~test message\x1b[201~\r` after the TUI is ready.
    - Confirm Codex does not panic and the pasted text is handled normally.
    
    Local validation:
    - `cargo test -p codex-tui wrapping::tests::`
    - `cargo test -p codex-tui -- --skip
    status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access
    --skip
    status::tests::status_permissions_full_disk_managed_without_network_is_external_sandbox`
  • Use plugin/list to get list of plugins for mentions (#22375)
    This switches TUI plugin mentions to use app-server `plugin/list` for
    plugin inventory and metadata instead of `PluginManager`, while keeping
    the same mention-eligibility filters as before.
    
    Same filters as before:
    - Only plugins in the current config / cwd scope.
    - Only installed and enabled plugins.
    - Only plugins that actually expose a capability, meaning at least one
    skill, MCP server, or app connector.
    - Uses `plugin/list` for the mention names/descriptions
  • Enable plugin hooks by default (#22549)
    # Why
    
    Plugin-bundled hooks are already wired through the plugin manager,
    session setup, and app-server hook listing paths. Keeping `plugin_hooks`
    disabled by default means users still need an explicit feature opt-in
    before that existing behavior participates in normal plugin loading.
    
    # What
    
    - mark `plugin_hooks` as stable and enable it by default
    - add feature-registry test coverage for the new default/stage pairing
    
    Validation:
    
    - `cargo test -p codex-features`
    - `just fmt`
  • Add callback ids to local MCP OAuth redirects (#20237)
    ## Summary
    
    - Add a deterministic callback-id path segment to local MCP OAuth
    redirect URIs before starting authorization.
    - Derive the callback id from the normalized MCP server URL and encode
    it as a 12-character URL-safe hash.
    - Reuse the existing exact callback-path validation so OAuth completion
    only succeeds on the callback path that was sent in the redirect URI.
    
    ## Context
    
    Slack thread:
    https://openai.slack.com/archives/C087WB3AGCR/p1777480566571699
    
    That thread calls out the OAuth mix-up class of issue for MCP servers.
    The connector/App Connect flow already has a callback_id concept that
    binds the OAuth callback URL to the MCP app/server identity. Codex
    desktop's local MCP OAuth flow was still using a generic local callback
    path like `/callback`, so this PR adds the same shape to the shared
    local MCP OAuth helper.
    
    ## Behavior
    
    Before this change, local MCP OAuth used:
    
    - default local callback URL: `http://127.0.0.1:<port>/callback`
    - configured callback URL: `<configured callback URL>` unchanged
    
    After this change, Codex appends a deterministic callback-id segment:
    
    - default local callback URL:
    `http://127.0.0.1:<port>/callback/<callback_id>`
    - configured callback URL: `<configured callback path>/<callback_id>`
    
    The local callback server already compares the incoming request path
    against the path from the redirect URI. By appending the callback id
    before both authorization and callback validation, callbacks that arrive
    on the old generic path or a mismatched callback-id path are rejected.
    
    The callback id is bound to the MCP endpoint URL, including path and
    query, so path-based multi-tenant MCP deployments on the same origin do
    not share a callback path. URL fragments are ignored because they are
    not sent to the server.
    
    The change lives in `codex-rmcp-client`, so it covers both the normal
    desktop MCP OAuth login path and silent/plugin-triggered MCP OAuth login
    paths that use the same `perform_oauth_login_*` helpers.
    
    ## Scope and non-goals
    
    - This does not change the app-server protocol or desktop webview
    request shape.
    - This does not implement RFC 9207 `iss` validation; issuer validation
    is still useful when providers return `iss`.
    - This does not make arbitrary untrusted MCP servers safe to use. It
    specifically adds callback URL binding for the local MCP OAuth flow.
    
    ## Validation
    
    - `cargo fmt --all`
    - `cargo test -p codex-rmcp-client perform_oauth_login`
  • Use selected environment cwd for filesystem helpers (#22542)
    ## Why
    
    `TurnContext::cwd` is deprecated in favor of resolving paths from the
    selected turn environment cwd. A few filesystem-oriented paths were
    still constructing sandbox context from the legacy cwd and then mutating
    it afterward, or resolving local file paths through the deprecated
    helper.
    
    ## What changed
    
    - Make `TurnContext::file_system_sandbox_context` take the trusted cwd
    explicitly.
    - Pass the selected turn environment cwd directly from `apply_patch` and
    `view_image` call sites.
    - Restrict `spawn_agents_on_csv` to exactly one local environment and
    resolve input/output CSV paths from that local environment cwd.
    - Remove a redundant test setup assignment that only synchronized
    deprecated `TurnContext::cwd` with a replaced config.
    
    ## Validation
    
    - `cargo test -p codex-core view_image`
    - `cargo test -p codex-core
    maybe_persist_mcp_tool_approval_writes_project_config_for_project_server`
    - `cargo test -p codex-core parse_csv_supports_quotes_and_commas`
    - `git diff --check`
  • Shard Bazel Windows tests across jobs (#22408)
    ## Summary
    - split the single PR-blocking Bazel Windows test leg into four Windows
    shard jobs
    - preserve the existing required Windows Bazel check name with a
    lightweight aggregate gate
    - keep Linux/macOS Bazel test jobs and the separate Windows
    clippy/release jobs unchanged
    
    ## Why
    The ordinary PR Windows Bazel test leg was one GitHub Actions job, so
    Bazel only had in-job parallelism. This gives that lane real job-level
    fanout across separate Windows hosts while keeping the target set
    disjoint via stable label hashing.
    
    ## Evidence
    - final pre-rebase green run: `25774733562`
    - Windows shard target counts: `61/212`, `48/212`, `52/212`, `51/212`
    - Windows test fanout completed in about 7m29s versus a recent
    monolithic median around 22m26s
    
    ## Notes
    - this is scoped to the Bazel Windows test leg only
    - each shard keeps the existing Windows cross-compile/RBE path and
    restores the former monolithic Windows test cache
    - shard jobs do not upload duplicate repository caches after test work,
    keeping cache cleanup off the PR-blocking shard path
    - no local validation run; relying on GitHub Actions for the
    workflow-shaped check
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: prevent codex-backend from stealing originator (#22533)
    ## Why
    
    Remote control starts by letting `codex-backend` initialize against the
    app-server as an infrastructure health/proxy client before the real
    remote client connects. App-server initialization also sets the
    process-wide `originator` from `client_info.name`, so `codex-backend`
    could become the sticky originator for later model/API requests even
    after the real client initialized.
    
    ## What changed
    
    - Treat `codex-backend` as a non-originating initialize client,
    alongside the existing `codex_app_server_daemon` probe client.
    - Preserve normal per-connection initialize behavior, including session
    metadata and initialize analytics.
    - Add regression coverage that verifies `codex-backend` initialize does
    not replace the default originator.
    
    ## Testing
    
    - `cargo test -p codex-app-server --test all
    initialize_codex_backend_does_not_override_originator`
  • chore(config) rm tools.view_image (#22501)
    ## Summary
    It appears this config flag has been broken/a noop for quite some time:
    since https://github.com/openai/codex/pull/8850. Let's simplify and get
    rid of this.
    
    ## Testing
    - [x] Updated unit tests
  • chore(config) rm Feature::CodexGitCommit (#22412)
    ## Summary
    Removes the unused Feature::CodexGitCommit
    
    ## Testing
    - [x] tests pass
  • [codex] Reuse Apps MCP path override for plugin-service rollout (#22527)
    ## Summary
    - reuse `apps_mcp_path_override` for the plugin-service rollout,
    defaulting enabled boolean overrides to `/ps/mcp` while preserving
    explicit configured paths
    
    ## Validation
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core apps_mcp_path_override`
    - `cargo test -p codex-core
    to_mcp_config_preserves_apps_feature_from_config`
    - `cargo test -p codex-features`
  • windows-sandbox: fail elevated setup when firewall policy is ineffective (#22353)
    ## Why
    
    Elevated Windows sandbox setup currently assumes that the firewall rules
    it writes will take effect. On managed Windows hosts, local firewall
    policy changes can be ignored or only partially apply across the active
    profiles, which means setup can appear to succeed without providing the
    expected network isolation.
    
    ## What changed
    
    - Query `INetFwPolicy2::LocalPolicyModifyState` before configuring the
    elevated sandbox firewall rules.
    - Fail setup when Windows reports that local firewall policy edits are
    ineffective or only apply to some current profiles.
    - Surface that condition with a dedicated
    `helper_firewall_policy_ineffective` setup error code so support and
    IT-facing diagnostics can distinguish it from COM access failures.
    - Add focused coverage for effective policy, group-policy override, and
    partial-profile coverage cases.
    
    ## Testing
    
    - `cargo test -p codex-windows-sandbox --bin
    codex-windows-sandbox-setup`
  • [codex] Scope Windows sandbox write-root capability SIDs (#21479)
    ## Summary
    - fix by scoping Windows workspace-write capability SIDs to active
    effective write roots
    - build legacy/elevated tokens from only the active effective write
    roots
    - align setup/audit deny ACL handling with active root-specific SIDs
    
    ## Testing
    - just fmt
    - git diff --check --cached
    - just argument-comment-lint
    - cargo check -p codex-windows-sandbox --locked (blocked by libwebrtc ->
    libyuv fetch: CONNECT tunnel failed, response 403)
  • Refactor chatwidget settings surfaces into modules (phase 4) (#22518)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow, and #22433 completed phase 3 by extracting protocol,
    replay, streaming, and tool lifecycle handling.
    
    This PR is phase 4. It keeps moving high-churn UI coordination out of
    the central widget by extracting settings, popups, and status surfaces
    without changing the visible behavior those flows already provide. This
    is once again a mechanical movement of existing functions. No functional
    changes.
    
    ## What Changed
    
    - Added focused modules for runtime settings/model coordination,
    model/reasoning/collaboration popups,
    settings/personality/theme/audio/experimental popups, permission
    prompts, status setup/output controls, and Windows sandbox prompt flows.
    - Moved the remaining rate-limit nudge/status helpers and connectors
    popup/loading/update helpers into their existing focused modules.
    - Preserved the existing picker flows, approval behavior, status/title
    setup previews, rate-limit notices, and connectors/app list behavior
    while shrinking `chatwidget.rs` back toward orchestration.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    composition surface for these extracted behaviors.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    Completed in #22433.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers. This PR.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer.
    
    ## Verification
    
    - `cargo check -p codex-tui`
    - `cargo test -p codex-tui chatwidget::tests::permissions`
    - `cargo test -p codex-tui chatwidget::tests::status_surface_previews`
    - `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
    - `cargo test -p codex-tui chatwidget::tests::status_and_layout`
    
    `cargo test -p codex-tui` also compiles and begins running, but aborts
    in the unchanged app-side test
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
    with a reproducible stack overflow.
  • Deprecate TurnContext cwd and resolve_path (#22519)
    ## Why
    
    `TurnContext::cwd` and `TurnContext::resolve_path` are being phased out
    in favor of using the selected turn environment cwd directly.
    Deprecating both APIs makes any new direct dependency visible while
    preserving the existing migration path for current callers.
    
    ## What Changed
    
    - Marked `TurnContext::cwd` and `TurnContext::resolve_path` as
    deprecated with guidance to use the selected turn environment cwd
    instead.
    - Added exact `#[allow(deprecated)]` suppressions at each existing
    direct usage site, including tests, rather than adding crate-wide
    suppression.
    - Kept the change behavior-preserving: current cwd reads, writes, and
    path resolution continue to use the same values.
    
    ## Verification
    
    - `just fmt`
    - `cargo check -p codex-core`
    - `cargo check -p codex-core --tests`
    - `git diff --check`
  • Pass Codex product SKU to ChatGPT backend (#22366)
    # Description
    
    We need to set the appropriate Product SKU for full functionality for
    the apps endpoints for each type of client
    
    # Testing
    
    `./target/debug/codex --enable app`
    
    <img width="1786" height="398" alt="CleanShot 2026-05-12 at 11 51 25@2x"
    src="https://github.com/user-attachments/assets/2142f768-fc72-4fcb-8f39-9bd0d8569170"
    />
    
    Regular slack flows seem to work, also curling these endpoints with the
    correct SKU returns the right apps
  • feat: expose multi-agent v2 as model-only tools (#22514)
    ## Why
    
    `code_mode_only` filters code-mode nested tools out of the top-level
    tool list. For multi-agent v2, we need a rollout shape where the
    collaboration tools remain callable as normal model tools without also
    being embedded into the code-mode `exec` tool declaration.
    
    Related to this:
    https://openai-corpws.slack.com/archives/C0AQLHB4U75/p1778660267922549
    
    ## What Changed
    
    - Adds `features.multi_agent_v2.non_code_mode_only`, including config
    resolution, profile override handling, and generated schema coverage.
    - Introduces `ToolExposure::DirectModelOnly` so a tool can be included
    in the initial model-visible list while staying out of the nested
    code-mode tool surface.
    - Applies that exposure to the multi-agent v2 tools when the new flag is
    set: `spawn_agent`, `send_message`, `followup_task`, `wait_agent`,
    `close_agent`, and `list_agents`.
    - Updates code-mode-only filtering so direct-model-only tools remain
    visible while ordinary nested code-mode tools are still hidden.
    
    ## Verification
    
    - Added config parsing/profile tests for `non_code_mode_only`.
    - Added tool spec coverage for the code-mode-only multi-agent v2
    exposure behavior.
  • [codex] Remove unused legacy shell tools (#22246)
    ## Why
    
    Recent session history showed no active use of the raw `shell`,
    `local_shell`, or `container.exec` execution surfaces. Keeping those
    handlers/specs wired into core leaves duplicate shell execution paths
    alongside the supported `shell_command` and unified exec tools.
    
    ## What changed
    
    - Removed the raw `shell` handler/spec and its `ShellToolCallParams`
    protocol helper.
    - Removed the legacy `local_shell` and `container.exec` handler/spec
    plumbing while preserving persisted-history compatibility for old
    response items.
    - Normalized model/config `default` and `local` shell selections to
    `shell_command`.
    - Pruned tests that exercised removed raw-shell/local-shell/apply-patch
    variants and kept coverage on `shell_command`, unified exec, and
    freeform `apply_patch`.
    
    ## Verification
    
    - `git diff --check`
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::handlers::shell`
    - `cargo test -p codex-core tools::spec`
    - `cargo test -p codex-core tools::router`
    - `cargo test -p codex-core
    active_call_preserves_triggering_command_context`
    - `cargo test -p codex-core guardian_tests`
    - `cargo test -p codex-core --test all shell_serialization`
    - `cargo test -p codex-core --test all apply_patch_cli`
    - `cargo test -p codex-core --test all shell_command_`
    - `cargo test -p codex-core --test all local_shell`
    - `cargo test -p codex-core --test all otel::`
    - `cargo test -p codex-core --test all hooks::`
    - `just fix -p codex-core`
    - `just fix -p codex-tools`
  • fix: drop underscored id headers (#22193)
    ## Why
    Stop sending duplicate `session_id`/`thread_id` headers. We only want
    the hyphenated forms as `_` is rejected by some proxies
    
    Related discussion here:
    https://openai.slack.com/archives/C095U48JNL9/p1778508316923179
    
    ## What
    - Keep `session-id` and `thread-id`
    - Remove the underscore aliases
  • Introduce tool exposure for deferred registration (#22489)
    ## Why
    
    Deferred tools were tracked with separate side-channel filtering after
    tool specs had already been assembled. That made the registry
    responsible for executing tools while the router/spec planner separately
    decided whether those same tools should be exposed to the model up
    front.
    
    This PR makes exposure part of the tool handler contract so direct
    versus deferred availability travels with the executable tool
    registration.
    
    Next step will be to simplify registration
    
    ## What Changed
    
    - Adds `ToolExposure` to `codex-tools` and exposes it through
    `ToolExecutor`, defaulting tools to `Direct`.
    - Teaches dynamic tools and MCP handlers to mark deferred tools as
    `Deferred` at construction time.
    - Renames the registry object-safe wrapper from `AnyToolHandler` to
    `RegisteredTool` and uses `ToolExposure` when deciding whether to
    include a handler's spec in the initial model-visible tool list.
    - Refactors tool spec planning to derive direct specs and deferred
    search entries from registered handlers, removing the router's
    special-case deferred dynamic tool filtering.
    
    ## Verification
    
    - Not run.
  • config: add strict config parsing (#20559)
    ## Why
    
    Codex intentionally ignores unknown `config.toml` fields by default so
    older and newer config files keep working across versions. That leniency
    also makes typo detection hard because misspelled or misplaced keys
    disappear silently.
    
    This change adds an opt-in strict config mode so users and tooling can
    fail fast on unrecognized config fields without changing the default
    permissive behavior.
    
    This feature is possible because `serde_ignored` exposes the exact
    signal Codex needs: it lets Codex run ordinary Serde deserialization
    while recording fields Serde would otherwise ignore. That avoids
    requiring `#[serde(deny_unknown_fields)]` across every config type and
    keeps strict validation opt-in around the existing config model.
    
    ## What Changed
    
    ### Added strict config validation
    
    - Added `serde_ignored`-based validation for `ConfigToml` in
    `codex-rs/config/src/strict_config.rs`.
    - Combined `serde_ignored` with `serde_path_to_error` so strict mode
    preserves typed config error paths while also collecting fields Serde
    would otherwise ignore.
    - Added strict-mode validation for unknown `[features]` keys, including
    keys that would otherwise be accepted by `FeaturesToml`'s flattened
    boolean map.
    - Kept typed config errors ahead of ignored-field reporting, so
    malformed known fields are reported before unknown-field diagnostics.
    - Added source-range diagnostics for top-level and nested unknown config
    fields, including non-file managed preference source names.
    
    ### Kept parsing single-pass per source
    
    - Reworked file and managed-config loading so strict validation reuses
    the already parsed `TomlValue` for that source.
    - For actual config files and managed config strings, the loader now
    reads once, parses once, and validates that same parsed value instead of
    deserializing multiple times.
    - Validated `-c` / `--config` override layers with the same
    base-directory context used for normal relative-path resolution, so
    unknown override keys are still reported when another override contains
    a relative path.
    
    ### Scoped `--strict-config` to config-heavy entry points
    
    - Added support for `--strict-config` on the main config-loading entry
    points where it is most useful:
      - `codex`
      - `codex resume`
      - `codex fork`
      - `codex exec`
      - `codex review`
      - `codex mcp-server`
      - `codex app-server` when running the server itself
      - the standalone `codex-app-server` binary
      - the standalone `codex-exec` binary
    - Commands outside that set now reject `--strict-config` early with
    targeted errors instead of accepting it everywhere through shared CLI
    plumbing.
    - `codex app-server` subcommands such as `proxy`, `daemon`, and
    `generate-*` are intentionally excluded from the first rollout.
    - When app-server strict mode sees invalid config, app-server exits with
    the config error instead of logging a warning and continuing with
    defaults.
    - Introduced a dedicated `ReviewCommand` wrapper in `codex-rs/cli`
    instead of extending shared `ReviewArgs`, so `--strict-config` stays on
    the outer config-loading command surface and does not become part of the
    reusable review payload used by `codex exec review`.
    
    ### Coverage
    
    - Added tests for top-level and nested unknown config fields, unknown
    `[features]` keys, typed-error precedence, source-location reporting,
    and non-file managed preference source names.
    - Added CLI coverage showing invalid `--enable`, invalid `--disable`,
    and unknown `-c` overrides still error when `--strict-config` is
    present, including compound-looking feature names such as
    `multi_agent_v2.subagent_usage_hint_text`.
    - Added integration coverage showing both `codex app-server
    --strict-config` and standalone `codex-app-server --strict-config` exit
    with an error for unknown config fields instead of starting with
    fallback defaults.
    - Added coverage showing unsupported command surfaces reject
    `--strict-config` with explicit errors.
    
    ## Example Usage
    
    Run Codex with strict config validation enabled:
    
    ```shell
    codex --strict-config
    ```
    
    Strict config mode is also available on the supported config-heavy
    subcommands:
    
    ```shell
    codex --strict-config exec "explain this repository"
    codex review --strict-config --uncommitted
    codex mcp-server --strict-config
    codex app-server --strict-config --listen off
    codex-app-server --strict-config --listen off
    ```
    
    For example, if `~/.codex/config.toml` contains a typo in a key name:
    
    ```toml
    model = "gpt-5"
    approval_polic = "on-request"
    ```
    
    then `codex --strict-config` reports the misspelled key instead of
    silently ignoring it. The path is shortened to `~` here for readability:
    
    ```text
    $ codex --strict-config
    Error loading config.toml:
    ~/.codex/config.toml:2:1: unknown configuration field `approval_polic`
      |
    2 | approval_polic = "on-request"
      | ^^^^^^^^^^^^^^
    ```
    
    Without `--strict-config`, Codex keeps the existing permissive behavior
    and ignores the unknown key.
    
    Strict config mode also validates ad-hoc `-c` / `--config` overrides:
    
    ```text
    $ codex --strict-config -c foo=bar
    Error: unknown configuration field `foo` in -c/--config override
    
    $ codex --strict-config -c features.foo=true
    Error: unknown configuration field `features.foo` in -c/--config override
    ```
    
    Invalid feature toggles are rejected too, including values that look
    like nested config paths:
    
    ```text
    $ codex --strict-config --enable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --disable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --enable multi_agent_v2.subagent_usage_hint_text
    Error: Unknown feature flag: multi_agent_v2.subagent_usage_hint_text
    ```
    
    Unsupported commands reject the flag explicitly:
    
    ```text
    $ codex --strict-config cloud list
    Error: `--strict-config` is not supported for `codex cloud`
    ```
    
    ## Verification
    
    The `codex-cli` `strict_config` tests cover invalid `--enable`, invalid
    `--disable`, the compound `multi_agent_v2.subagent_usage_hint_text`
    case, unknown `-c` overrides, app-server strict startup failure through
    `codex app-server`, and rejection for unsupported commands such as
    `codex cloud`, `codex mcp`, `codex remote-control`, and `codex
    app-server proxy`.
    
    The config and config-loader tests cover unknown top-level fields,
    unknown nested fields, unknown `[features]` keys, source-location
    reporting, non-file managed config sources, and `-c` validation for keys
    such as `features.foo`.
    
    The app-server test suite covers standalone `codex-app-server
    --strict-config` startup failure for an unknown config field.
    
    ## Documentation
    
    The Codex CLI docs on developers.openai.com/codex should mention
    `--strict-config` as an opt-in validation mode for supported
    config-heavy entry points once this ships.
  • [rollout-trace] Add a trace ID to MCP calls. (#22326)
    This allows us to connect individual tool calls to the logs of the
    invocations.
  • fix: prevent fmt from updating Python SDK lockfile (#22505)
    ## Why
    
    `just fmt` should align source formatting without resolving dependencies
    or rewriting lockfiles. The Python SDK formatting steps run through
    `uv`, so differing local `uv` versions could decide the SDK lock was
    stale and mutate `sdk/python/uv.lock` before Ruff ran.
    
    ## What
    
    - Add `--frozen` to both Python SDK `uv run ... ruff` commands in the
    root `fmt` recipe.
    - Update the existing Python SDK artifact workflow guard test so future
    changes keep the formatter recipe non-lock-mutating.
    
    ## Verification
    
    - `uv run --frozen --project ../sdk/python --extra dev pytest
    ../sdk/python/tests/test_artifact_workflow_and_binaries.py -q`
  • Refactor chatwidget protocol flows into modules (phase 3) (#22433)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow.
    
    This PR is phase 3. It keeps moving high-churn event handling out of the
    central widget by extracting protocol, replay, streaming, and tool
    lifecycle handling without changing the visible behavior those flows
    already provide. This is once again just a mechanical movement of
    existing functions. No functional changes.
    
    ## What Changed
    
    - Added focused modules for protocol request dispatch, replay rendering,
    assistant/plan/reasoning streaming, turn runtime bookkeeping, hook
    lifecycle handling, command lifecycle handling, tool lifecycle
    rendering, and interactive tool request prompts.
    - Kept active-cell grouping, transcript invalidation, interrupt
    deferral, and final-message separator behavior in the same flows, just
    moved into smaller files.
    - Added module header comments to the new files so the ownership
    boundaries are explicit.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    orchestration surface for these extracted behaviors.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    This PR.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer.
  • refactor: split memories extension crate modules (#22500)
    ## Why
    
    The memories extension has several distinct responsibilities:
    registering its prompt and tool contributors, enforcing local-memory
    filesystem boundaries, implementing list/read/search behavior, and
    wrapping that backend as extension tools. Those responsibilities were
    concentrated in `lib.rs`, `local.rs`, and the tool modules, which made
    follow-up work harder to review and risked growing files through
    unrelated edits.
    
    This PR reorganizes the crate so each responsibility has a narrower
    owner while preserving the same extension entrypoint and memory tool
    behavior.
    
    ## What Changed
    
    - Moved extension lifecycle, prompt, and tool registration into
    `src/extension.rs`, leaving `src/lib.rs` as the small crate entrypoint.
    - Split `LocalMemoriesBackend` helpers into `local/list.rs`,
    `local/path.rs`, `local/read.rs`, and `local/search.rs`.
    - Centralized tool names and limits at the crate level, and kept the
    backend and extension implementation crate-private.
    - Made `memory_list`, `memory_read`, and `memory_search` tool executors
    generic over `MemoriesBackend`, so tests can exercise the full executor
    path without depending on tool internals.
    - Consolidated and expanded memory extension tests in `src/tests.rs`,
    including read/search tool output coverage, multi-query search, windowed
    `all_within_lines`, and legacy `query` rejection.
    
    ## Testing
    
    - Not run locally.
  • feat(tui): standardize picker navigation keys (#22347)
    ## Why
    
    Picker-style UI in the TUI has accumulated a mix of hardcoded navigation
    keys. Some lists supported page movement, some did not; some accepted
    Vim-like keys, while others only accepted arrows; and tabbed or
    horizontally adjustable pickers had no shared keymap action for
    left/right movement.
    
    This PR makes picker/list navigation consistent and configurable so
    users can rely on the same defaults across the TUI.
    
    ## What Changed
    
    - Adds shared list keymap actions for:
      - vertical movement: `move_up`, `move_down`
      - horizontal movement: `move_left`, `move_right`
      - paging and jumps: `page_up`, `page_down`, `jump_top`, `jump_bottom`
    - Adds defaults:
    - Up/down: arrows, `Ctrl+P/N`, `Ctrl+K/J`, and plain `k/j` where text
    input is not active
      - Page up/down: `PageUp/PageDown` and `Ctrl+B/F`
      - First/last: `Home/End`
      - Left/right: `Left/Right` and `Ctrl+H/L`
    - Wires the shared list keymap through picker and list surfaces
    including session resume, multi-select, tabbed selection lists,
    settings-style lists, app-link selection, MCP elicitation,
    request-user-input, and the OSS selection wizard.
    - Keeps search behavior intact by reserving printable characters for
    query text in searchable pickers.
    - Updates keymap setup actions, config schema, snapshots, and focused
    coverage for the new list actions.
    
    ## How to Test
    
    1. Start Codex from this branch and open the session picker, for example
    with an existing session history.
    2. In the session list, verify that `Ctrl+J/K` moves the selection
    down/up.
    3. Verify that `Ctrl+F/B` pages down/up and `Home/End` jumps to the
    first/last visible session.
    4. Type printable search text such as `j` or `k` and confirm it updates
    the query instead of navigating.
    5. Focus a picker control that changes values horizontally, such as a
    session picker toolbar control, and verify `Ctrl+H/L` changes the
    focused value like left/right arrows.
    
    Targeted tests run:
    
    - `cargo test -p codex-tui keymap::tests::`
    - `cargo test -p codex-tui keymap_setup::tests::`
    - `cargo test -p codex-tui horizontal_list_keys`
    - `cargo test -p codex-tui page_and_jump_navigation_use_list_keymap`
    - `cargo test -p codex-tui ctrl_h_l_move_provider_selection`
    - `cargo test -p codex-tui scroll_state::tests`
    - `cargo test -p codex-tui
    switching_tabs_changes_visible_items_and_clears_search`
    - `cargo test -p codex-tui toggle_sort_key_reloads_with_new_sort`
    
    Also ran `just write-config-schema`, `just fmt`, `just fix -p
    codex-tui`, `just argument-comment-lint`, and `git diff --check`.
    
    Note: `cargo test -p codex-tui` was attempted and still aborts in the
    pre-existing
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all` stack
    overflow, which is unrelated to this branch.
  • fix: main (#22503)
    Fix main due to conflicting merge
  • feat: memories ext (#22498)
    First memories extension implementation
    Based on memories-mcp tools
  • feat: add config-change extension contributor (#22488)
    ## Why
    
    Extensions can observe thread and turn lifecycle events today, but there
    was no single host-owned hook for changes to the effective thread
    configuration. That makes features that need to react to model,
    permission, or tool-suggest updates either depend on individual mutation
    paths or risk going stale after runtime config refreshes.
    
    This adds a typed config-change contributor so extension-owned state can
    stay synchronized with the effective thread config while the host
    remains responsible for deciding when config changed.
    
    ## What Changed
    
    - Added `ConfigContributor<C>` to `codex_extension_api`, with
    before/after immutable snapshots of the effective config plus
    session/thread extension stores.
    - Added registry builder/accessor support through `config_contributor`
    and `config_contributors`.
    - Emits config-change callbacks after committed updates from session
    settings, per-turn setting updates, and `refresh_runtime_config`.
    - Builds effective config snapshots only when config contributors are
    registered, and suppresses no-op callbacks when the before/after
    snapshots are equal.
    - Added a core session regression test that verifies contributors
    observe both model changes and user-layer runtime config changes,
    including access to session and thread extension stores.
    
    ## Validation
    
    Added `config_change_contributor_observes_effective_config_changes` in
    `codex-rs/core/src/session/tests.rs` to cover the new contributor path.
  • Add service tier overrides to spawned agents (#22139)
    ## Why
    
    Spawned agents can already override `model` and `reasoning_effort`, but
    they have no equivalent way to opt into a model-supported service tier.
    That makes it impossible to preserve or intentionally select tiered
    execution behavior when delegating work to a sub-agent, even though the
    model catalog already advertises supported `service_tiers`.
    
    ## What changed
    
    - Add optional `service_tier` to both legacy and `MultiAgentV2`
    `spawn_agent` tool inputs.
    - Show each picker-visible model's supported service tier ids and
    descriptions in the `spawn_agent` tool guidance.
    - Resolve service tier selection after the child agent's effective model
    is known.
    - Inherit the parent tier when omitted and still supported by the final
    child model; otherwise clear it.
    - Reject explicit unsupported tier requests with a model-facing error.
    - Keep explicit `service_tier` usable on full-history forks, while still
    honoring the existing model/reasoning fork restrictions.
    - Hide `service_tier` alongside other spawn metadata when
    `hide_spawn_agent_metadata` is enabled.
    
    ## Verification
    
    Added focused coverage for:
    
    - v1/v2 `spawn_agent` schema exposure for `service_tier`
    - tier descriptions in spawn guidance
    - hidden-metadata suppression
    - explicit supported tier selection
    - explicit unknown and unsupported tier rejection
    - inherited tier preservation or clearing based on child-model support
    - full-history fork acceptance for explicit service tiers in both v1 and
    v2
    
    Local Rust tests were not run in this workspace per repo guidance; the
    new coverage is included for CI.
  • feat(tui): remove Zellij TUI workarounds (#22214)
    ## Why
    
    We added Zellij-specific TUI workarounds because older Zellij behavior
    did not work with Codex's normal terminal model:
    
    - #8555 made `tui.alternate_screen = "auto"` disable alternate screen in
    Zellij so transcript history stayed available.
    - #16578 avoided scroll-region operations in Zellij by emitting raw
    newlines and using a separate composer styling path.
    
    This PR removes both workarounds because the latest Zellij release
    tested locally (`zellij 0.44.1`) works correctly with Codex's standard
    TUI behavior: normal alternate-screen handling, redraw, and history
    insertion.
    
    ## What Changed
    
    - Removed the `InsertHistoryMode::Zellij` path and the Zellij-only
    newline scrollback insertion behavior.
    - Removed cached `is_zellij` state from the TUI and composer.
    - Removed Zellij-specific composer styling, the helper snapshot, and the
    `TerminalInfo::is_zellij()` convenience method that only served this
    workaround.
    - Changed `tui.alternate_screen = "auto"` to use alternate screen for
    Zellij too; `--no-alt-screen` and `tui.alternate_screen = "never"` still
    preserve the inline mode escape hatch.
    - Updated the generated config schema description for
    `tui.alternate_screen`.
    
    ## How to Test
    
    Manual smoke path used with `zellij 0.44.1`:
    
    1. Build and run this branch inside a Zellij `0.44.1` session with
    default config.
    2. Start Codex normally and produce enough assistant/tool output to
    create scrollback.
    3. Confirm the transcript remains readable, the composer renders
    normally, and scrolling through terminal history works.
    4. Resize the Zellij pane while output exists and confirm the TUI
    redraws without duplicated, missing, or stale rows.
    5. Compare with `--no-alt-screen` or `-c tui.alternate_screen=never` if
    you want to verify the inline fallback still works.
    
    Targeted tests:
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `cargo test -p codex-terminal-detection`
    - `cargo test -p codex-tui alternate_screen_auto_uses_alt_screen`
    
    Attempted but did not complete locally:
    - `cargo test -p codex-tui` built and ran the new test successfully,
    then failed later on unrelated local failures in
    `status_permissions_full_disk_managed_*` and a stack overflow in
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
    
    ## Documentation
    
    No developers.openai.com Codex documentation update is needed for this
    revert.
  • Make context contributors async (#22491)
    ## Summary
    - make ContextContributor return a boxed Send future
    - await context contributors during initial context assembly
    - update existing contributors and extension-api examples for the async
    contract
    
    ## Testing
    - cargo test -p codex-extension-api --examples
    - cargo test -p codex-git-attribution
    - cargo test -p codex-core
    build_initial_context_includes_git_attribution_from_extensions --
    --nocapture
    - cargo test -p codex-core
    build_initial_context_omits_git_attribution_when_feature_is_disabled --
    --nocapture
    - cargo test -p codex-core (fails in unrelated
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
    stack overflow)
    - just fix -p codex-extension-api
    - just fix -p codex-git-attribution
    - just fix -p codex-core
    - cargo clippy -p codex-extension-api --examples
  • feat: move extension scope ids into ExtensionData (#22490)
    ## Summary
    - add a scoped level_id to ExtensionData and expose it through
    level_id()
    - remove thread_id/turn_id parameters from extension contributor inputs
    where the scoped ExtensionData already carries that identity
    - move turn-scoped extension data onto TurnContext so token usage and
    lifecycle contributors can share the same turn store
    
    ## Testing
    - cargo check -p codex-extension-api -p codex-core --tests
    - cargo test -p codex-extension-api
    - cargo test -p codex-guardian
    - cargo test -p codex-core --lib
    record_token_usage_info_notifies_extension_contributors
    - cargo test -p codex-core --lib
    submission_loop_channel_close_emits_thread_stop_lifecycle
    - cargo test -p codex-core --lib
    submission_loop_channel_close_aborts_active_turn_before_thread_stop_lifecycle
    - just fix -p codex-extension-api
    - just fix -p codex-guardian
    - just fix -p codex-core
    - just fmt
    
    ## Note
    - Attempted cargo test -p codex-core; it aborted in
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
    with the existing stack overflow before the full suite completed.
  • Scope macOS signing secrets to release environment (#22443)
    ## Summary
    - Split macOS Rust release builds into a dedicated `build-macos` job
    - Attach the `macos-signing` environment only to the macOS signing/build
    job
    - Keep Linux release builds outside the Apple signing environment while
    preserving the existing shared release build steps
  • feat: add token usage contributor hook (#22485)
    ## Why
    
    Extensions need a stable place to observe token accounting after Codex
    folds model-provider usage into the session's cached `TokenUsageInfo`.
    Without a contributor hook, extension-owned features that need last-turn
    or cumulative token usage have to duplicate session plumbing or infer
    state from client-facing `TokenCount` notifications.
    
    ## What changed
    
    - Added `TokenUsageContributor` to `codex-extension-api`, passing
    session/thread `ExtensionData`, `ThreadId`, turn id, and the current
    `TokenUsageInfo`.
    - Added registry builder/storage support for token-usage contributors.
    - Invoked registered contributors from
    `Session::record_token_usage_info` after the session token cache is
    updated and before the client `TokenCount` notification is emitted.
    
    ## Testing
    
    - Added `record_token_usage_info_notifies_extension_contributors`,
    covering cumulative token usage updates and access to both extension
    stores.
  • fix: emit thread stop lifecycle on implicit shutdown (#22482)
    ## Why
    
    The thread lifecycle contributor hooks from #22476 should observe every
    session teardown. The explicit `Op::Shutdown` path already emitted
    `on_thread_stop`, but when `submission_loop` exited because its
    submission channel closed, it only tore down runtime services. That
    meant extensions could miss the thread-stop lifecycle signal on implicit
    runtime shutdown.
    
    ## What Changed
    
    - Split shared runtime teardown into `shutdown_runtime_services(...)`.
    - Split thread-stop lifecycle emission into
    `emit_thread_stop_lifecycle(...)`.
    - Reused those helpers from both explicit shutdown and the channel-close
    shutdown path.
    - Tracked whether `Op::Shutdown` was received so the explicit path does
    not double-emit lifecycle events after it exits the loop.
    - Added a regression test that closes the submission channel and asserts
    `ThreadLifecycleContributor::on_thread_stop` runs once with the expected
    thread/session stores.
    
    ## Testing
    
    - `cargo test -p codex-core
    submission_loop_channel_close_emits_thread_stop_lifecycle`
  • feat: add turn lifecycle contributors (#22480)
    ## Why
    
    Extensions can already contribute prompt, tool, turn-item, and
    thread-lifecycle behavior, but there was no explicit host-owned hook for
    per-turn setup and cleanup. That makes extension-private turn state
    awkward: an extension either has to stash it outside the turn lifecycle
    or depend on core runtime objects.
    
    This adds a small turn lifecycle boundary. Extensions receive stable
    identifiers plus the existing session, thread, and turn `ExtensionData`
    stores, while core keeps owning task scheduling, cancellation, and turn
    teardown.
    
    ## What Changed
    
    - Added `TurnLifecycleContributor` with `on_turn_start`, `on_turn_stop`,
    and `on_turn_abort` callbacks in `codex-rs/ext/extension-api`.
    - Added typed `TurnStartInput`, `TurnStopInput`, and `TurnAbortInput`
    payloads that expose `thread_id`, `turn_id`, `session_store`,
    `thread_store`, and `turn_store`.
    - Registered and re-exported turn lifecycle contributors through
    `ExtensionRegistry` and `ExtensionRegistryBuilder`.
    - Wired `Session` to emit turn start, stop, and abort callbacks from the
    existing turn/task lifecycle paths.
    - Carried the turn-scoped `ExtensionData` through `RunningTask` and
    `RemovedTask` so stop/abort callbacks receive the same turn store
    created at turn start.
    
    ## Verification
    
    - Not run locally.