Commit Graph

786 Commits

  • Move memory prompt injection to app-server extension (#22841)
    ## Why
    
    Memory prompt injection should be owned by the extension path that
    app-server composes at runtime, not by an inlined special case inside
    `codex-core`. This keeps `codex-core` focused on session orchestration
    while allowing the memories extension to own its app-server prompt
    behavior.
    
    ## What Changed
    
    - Registers `codex-memories-extension` in the app-server extension
    registry.
    - Moves the memory developer-instruction injection out of
    `core/src/session/mod.rs` and into the memories extension prompt
    contributor.
    - Adds config-change handling so the extension keeps its per-thread
    memory settings in sync after startup.
    - Leaves memories read/retrieval tools unregistered for now so this PR
    only changes prompt injection.
    - Removes the stale `cargo-shear` ignore now that app-server depends on
    the extension crate.
    
    ## Validation
    
    Not run locally; validation is left to CI.
  • permissions: support workspace roots in profiles (#22610)
    ## Why
    
    This is the configuration/model half of the alternative permissions
    migration we discussed as a comparison point for
    [#22401](https://github.com/openai/codex/pull/22401) and
    [#22402](https://github.com/openai/codex/pull/22402).
    
    The old `workspace-write` model mixes three concerns that we want to
    keep separate:
    - reusable profile rules that should stay immutable once selected
    - user/runtime workspace roots from `cwd`, `--add-dir`, and legacy
    workspace-write config
    - internal Codex writable roots such as memories, which should not be
    shown as user workspace roots
    
    This PR gives permission profiles first-class `workspace_roots` so users
    can opt multiple repositories into the same `:workspace_roots` rules
    without using broad absolute-path write grants. It also starts
    separating the raw selected profile from the effective runtime profile
    by making `Permissions` expose explicit accessors instead of public
    mutable fields.
    
    A representative `config.toml` looks like this:
    
    ```toml
    default_permissions = "dev"
    
    [permissions.dev.workspace_roots]
    "~/code/openai" = true
    "~/code/developers-website" = true
    
    [permissions.dev.filesystem.":workspace_roots"]
    "." = "write"
    ".codex" = "read"
    ".git" = "read"
    ".vscode" = "read"
    ```
    
    If Codex starts in `~/code/codex` with that profile selected, the
    effective workspace-root set becomes:
    - `~/code/codex` from the runtime `cwd`
    - `~/code/openai` from the profile
    - `~/code/developers-website` from the profile
    
    The `:workspace_roots` rules are materialized across each root, so
    `.git`, `.codex`, and `.vscode` stay scoped the same way everywhere.
    Runtime additions such as `--add-dir` can still layer on later stack
    entries without mutating the selected profile.
    
    ## Stack Shape
    
    This PR intentionally stops before the profile-identity cleanup in
    [#22683](https://github.com/openai/codex/pull/22683) so the base review
    stays focused on config loading, workspace-root materialization, and
    compatibility with legacy `workspace-write`.
    
    The representation in this PR is therefore transitional: `Permissions`
    carries enough state to distinguish the raw constrained profile from the
    effective runtime profile, and there are still call sites that must keep
    the active profile identity and constrained profile value in sync. The
    follow-up PR replaces that with a single resolved profile state
    (`ResolvedPermissionProfile` / `PermissionProfileState`) that keeps the
    profile id, immutable `PermissionProfile`, and profile-declared
    workspace roots together. That follow-up removes APIs such as
    `set_constrained_permission_profile_with_active_profile()` where
    separate arguments could drift out of sync.
    
    Downstream PRs then build on this base to switch app-server turn updates
    to profile ids plus runtime workspace roots and to finish the
    user-visible summary behavior. Reviewers should judge this PR as the
    workspace-roots foundation, not as the final in-memory shape of selected
    permission profiles.
    
    ## Review Guide
    
    Suggested review order:
    
    1. Start with `codex-rs/core/src/config/mod.rs`.
    This is the main shape change in the base slice. `Permissions` now
    stores a private raw `Constrained<PermissionProfile>` plus runtime
    `workspace_roots`. Callers use `permission_profile()` when they need the
    raw constrained value and `effective_permission_profile()` when they
    need a materialized runtime profile. As noted above,
    [#22683](https://github.com/openai/codex/pull/22683) replaces this
    transitional shape with a resolved profile state that keeps identity and
    profile data together.
    
    2. Review `codex-rs/config/src/permissions_toml.rs` and
    `codex-rs/core/src/config/permissions.rs`.
    These add `[permissions.<id>.workspace_roots]`, resolve enabled entries
    relative to the policy cwd, and keep `:workspace_roots` deny-read glob
    patterns symbolic until the actual roots are known.
    
    3. Review `codex-rs/protocol/src/permissions.rs` and
    `codex-rs/protocol/src/models.rs`.
    These add the policy/profile materialization helpers that expand exact
    `:workspace_roots` entries and scoped deny-read globs over every
    workspace root. This is also where `ActivePermissionProfileModification`
    is removed from the core model.
    
    4. Review the legacy bridge in
    `Config::load_from_base_config_with_overrides` and
    `Config::set_legacy_sandbox_policy`.
    This is where legacy `workspace-write` roots become runtime workspace
    roots, while Codex internal writable roots stay internal and do not
    appear as user-facing workspace roots.
    
    5. Then skim downstream call sites.
    The interesting pattern is raw-vs-effective access: state/proxy/bwrap
    paths keep the raw constrained profile, while execution, summaries, and
    user-visible status use the effective profile and workspace-root list.
    
    ## What Changed
    
    - added `[permissions.<id>.workspace_roots]` to the config model and
    schema
    - added runtime `workspace_roots` state to `Config`/`Permissions` and
    `ConfigOverrides`
    - made `Permissions` profile fields private and replaced direct mutation
    with accessors/setters
    - added `PermissionProfile` and `FileSystemSandboxPolicy` helpers for
    materializing `:workspace_roots` exact paths and deny-read globs across
    all roots
    - moved legacy additional writable roots into runtime workspace-root
    state instead of active profile modifications
    - removed `ActivePermissionProfileModification` and its app-server
    protocol/schema export
    - updated sandbox/status summary paths so internal writable roots are
    not reported as user workspace roots
    
    ## Verification Strategy
    
    The targeted tests cover the behavior at the layers where regressions
    are most likely:
    - `codex-rs/core/src/config/config_tests.rs` verifies config loading,
    legacy workspace-root seeding, effective profile materialization, and
    memory-root handling.
    - `codex-rs/core/src/config/permissions_tests.rs` verifies profile
    `workspace_roots` parsing and `:workspace_roots` scoped/glob
    compilation.
    - `codex-rs/protocol/src/permissions.rs` unit tests verify exact and
    glob materialization over multiple workspace roots.
    - `codex-rs/tui/src/status/tests.rs` and
    `codex-rs/utils/sandbox-summary/src/sandbox_summary.rs` verify the
    user-facing summaries show effective workspace roots and hide internal
    writes.
    
    I also ran `cargo check --tests` locally after the latest stack refresh
    to catch cross-crate API breakage from the private-field/accessor
    changes.
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22610).
    * #22612
    * #22611
    * #22683
    * __->__ #22610
  • Trim TUI legacy core helper usage (#22695)
    ## Why
    
    The TUI still had a few low-risk dependencies flowing through the
    transitional `legacy_core` namespace after the app-server migration.
    These helpers either already have clearer non-core owners or are
    presentation logic that does not belong in `codex-core`, so moving them
    out reduces the compatibility surface without changing product behavior.
    
    ## What changed
    
    This is a low-risk change, almost completely mechanical in nature.
    
    - Route TUI Codex-home lookup through `codex-utils-home-dir`, use
    `Config::log_dir` directly, and call
    `codex-sandboxing::system_bwrap_warning` without going through
    `legacy_core`.
    - Move shared `codex resume` hint formatting from `codex-core` into
    `codex-utils-cli`.
    - Update CLI and TUI call sites to use the shared CLI utility, and keep
    the resume-command behavior covered by tests in its new home.
    
    ## Verification
    
    - `cargo test -p codex-utils-cli`
    - `cargo test -p codex-utils-cli resume_command`
  • [codex] add plugin marketplace CLI commands (#21396)
    ## Why
    
    Plugin CLI installs should behave more like `apt-get install`:
    configured marketplaces are the only install sources, the local
    marketplace snapshot is the package index used at install time, and
    `plugins/cache` is only a cache of already-downloaded plugin bytes.
    
    That distinction matters once marketplaces and plugins have auth or
    availability state. A repo-local marketplace manifest or leftover cached
    plugin artifact should not silently become an install source unless the
    marketplace was explicitly configured and its readable snapshot still
    authorizes the plugin.
    
    ## What Changed
    
    - add CLI commands to list configured marketplaces and add, list, or
    remove marketplace plugins
    - accept stable `plugin@marketplace` ids for add/remove while preserving
    the explicit `--marketplace` form
    - restrict `codex plugin add` and `codex plugin list` to configured
    marketplaces instead of also discovering current-working-directory
    marketplace roots
    - fail `codex plugin add` and `codex plugin list` when a configured
    marketplace snapshot is missing or malformed instead of treating it as
    an empty source or a generic plugin miss
    - preserve marketplace snapshot semantics: a configured local/Git
    marketplace snapshot can authorize installs without consulting the
    original upstream source
    - allow `plugins/cache` reuse only after configured marketplace
    resolution succeeds
    - keep removal resilient after marketplace deletion or drift and ignore
    malformed marketplace config entries in listing
    
    ## Commands Added
    
    - `codex plugin add <plugin>@<marketplace>`
    - `codex plugin add <plugin> --marketplace <marketplace>`
    - `codex plugin list`
    - `codex plugin list --marketplace <marketplace>`
    - `codex plugin remove <plugin>@<marketplace>`
    - `codex plugin remove <plugin> --marketplace <marketplace>`
    - `codex plugin marketplace add <source>`
    - `codex plugin marketplace add <source> --ref <ref>`
    - `codex plugin marketplace add <source> --sparse <path>`
    - `codex plugin marketplace list`
    - `codex plugin marketplace upgrade`
    - `codex plugin marketplace upgrade <marketplace>`
    - `codex plugin marketplace remove <marketplace>`
    
    ## CLI Help Output
    
    <details>
    <summary><code>codex plugin --help</code></summary>
    
    ```text
    Manage Codex plugins
    
    Usage: codex plugin [OPTIONS] <COMMAND>
    
    Commands:
      add          Install a plugin from a configured marketplace snapshot
      list         List plugins available from configured marketplace snapshots
      marketplace  Add, list, upgrade, or remove configured plugin marketplaces
      remove       Remove an installed plugin from local config and cache
      help         Print this message or the help of the given subcommand(s)
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin add --help</code></summary>
    
    ```text
    Install a plugin from a configured marketplace snapshot.
    
    Pass either `PLUGIN@MARKETPLACE` or pass `PLUGIN` with `--marketplace MARKETPLACE`.
    
    Usage: codex plugin add [OPTIONS] <PLUGIN[@MARKETPLACE]>
    
    Arguments:
      <PLUGIN[@MARKETPLACE]>
              Plugin selector to install: either PLUGIN@MARKETPLACE or PLUGIN with --marketplace
    
    Options:
      -m, --marketplace <MARKETPLACE>
              Configured marketplace name to use when PLUGIN does not include @MARKETPLACE
    
    Examples:
      codex plugin add sample@debug
      codex plugin add sample --marketplace debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin list --help</code></summary>
    
    ```text
    List plugins available from configured marketplace snapshots
    
    Usage: codex plugin list [OPTIONS]
    
    Options:
      -m, --marketplace <MARKETPLACE>
              Only list plugins from this configured marketplace name
    
    Examples:
      codex plugin list
      codex plugin list --marketplace debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin remove --help</code></summary>
    
    ```text
    Remove an installed plugin from local config and cache.
    
    Pass either `PLUGIN@MARKETPLACE` or pass `PLUGIN` with `--marketplace MARKETPLACE`.
    
    Usage: codex plugin remove [OPTIONS] <PLUGIN[@MARKETPLACE]>
    
    Arguments:
      <PLUGIN[@MARKETPLACE]>
              Plugin selector to remove: either PLUGIN@MARKETPLACE or PLUGIN with --marketplace
    
    Options:
      -m, --marketplace <MARKETPLACE>
              Marketplace name to use when PLUGIN does not include @MARKETPLACE
    
    Examples:
      codex plugin remove sample@debug
      codex plugin remove sample --marketplace debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace --help</code></summary>
    
    ```text
    Add, list, upgrade, or remove configured plugin marketplaces
    
    Usage: codex plugin marketplace [OPTIONS] <COMMAND>
    
    Commands:
      add      Add a local or Git marketplace to the configured marketplace sources
      list     List configured marketplace names and their local snapshot roots
      upgrade  Refresh configured Git marketplace snapshots
      remove   Remove a configured marketplace source by name
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace add --help</code></summary>
    
    ```text
    Add a local or Git marketplace to the configured marketplace sources
    
    Usage: codex plugin marketplace add [OPTIONS] <SOURCE>
    
    Arguments:
      <SOURCE>
              Marketplace source: a local path, owner/repo[@ref], HTTPS Git URL, or SSH Git URL
    
    Options:
          --ref <REF>
              Git ref to fetch for Git marketplace sources
    
          --sparse <PATH>
              Sparse checkout path for Git marketplace sources. Can be repeated
    
    Examples:
      codex plugin marketplace add ./path/to/marketplace
      codex plugin marketplace add owner/repo --ref main
      codex plugin marketplace add https://github.com/owner/repo --sparse plugins/foo
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace list --help</code></summary>
    
    ```text
    List configured marketplace names and their local snapshot roots
    
    Usage: codex plugin marketplace list [OPTIONS]
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace upgrade --help</code></summary>
    
    ```text
    Refresh configured Git marketplace snapshots.
    
    Omit MARKETPLACE_NAME to upgrade all configured Git marketplaces.
    
    Usage: codex plugin marketplace upgrade [OPTIONS] [MARKETPLACE_NAME]
    
    Arguments:
      [MARKETPLACE_NAME]
              Optional configured marketplace name to upgrade. Omit to upgrade all Git marketplaces
    
    Examples:
      codex plugin marketplace upgrade
      codex plugin marketplace upgrade debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace remove --help</code></summary>
    
    ```text
    Remove a configured marketplace source by name
    
    Usage: codex plugin marketplace remove [OPTIONS] <MARKETPLACE_NAME>
    
    Arguments:
      <MARKETPLACE_NAME>
              Configured marketplace name to remove
    
    Example:
      codex plugin marketplace remove debug
    ```
    
    </details>
    
    ## Public Semantics
    
    - `codex plugin add <plugin>@<marketplace>` succeeds only when
    `<marketplace>` is configured and its local marketplace snapshot
    contains `<plugin>`
    - repo-local marketplaces are not install sources until the user runs
    `codex plugin marketplace add ...`
    - configured marketplace snapshots must be readable; missing or
    malformed snapshots fail the CLI operation rather than silently falling
    through to cache or empty results
    - cached plugin artifacts can satisfy reinstall only when the configured
    marketplace snapshot still authorizes that plugin
    - cached plugin artifacts alone never make a plugin installable
    
    ## Tests
    
    - `cargo test -p codex-cli --test plugin_cli`
    - `cargo clippy -p codex-cli --tests -- -D warnings`
    - `cargo test -p codex-cli`
    - `git diff --check`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
  • feat: make ToolExecutor an async trait (#22560)
    ## Why
    
    `codex_tools::ToolExecutor` keeps a tool spec attached to its runtime
    handler, but extension tools still carried a parallel
    `ExtensionToolFuture` / `ExtensionToolExecutor` shape. That made
    extension-owned tools look different from host tools even though
    routing, registration, and execution need the same abstraction.
    
    This PR makes the shared executor contract directly async and lets
    extension tools implement it too, so host tools and extension tools can
    move through the same registration path.
    
    ## What changed
    
    - Changed `ToolExecutor::handle` to an `async fn` using `async-trait`,
    and updated built-in tool handlers to implement the async trait
    directly.
    - Replaced the bespoke `ExtensionToolFuture` contract with a marker
    `ExtensionToolExecutor` over `ToolExecutor<ToolCall, Output =
    JsonToolOutput>`, re-exporting `ToolExecutor` from
    `codex-extension-api`.
    - Updated the memories extension tools to implement the shared executor
    trait.
    - Split tool-router construction into collected executors plus hosted
    model specs, keeping hosted tools like web search and image generation
    separate from executable handlers.
    - Updated spec/router tests and extension-tool stubs for the new
    executor shape.
    
    ## Verification
    
    - Not run locally.
  • Simplify TUI startup test coverage (#22573)
    ## Why
    
    The TUI startup test surface had drifted into expensive, brittle
    coverage:
    
    - `tui/tests/suite/no_panic_on_startup.rs` was already ignored as flaky
    while still spawning a PTY to exercise malformed exec-policy rules.
    - `tui/tests/suite/model_availability_nux.rs` used a seeded session,
    cursor-query spoofing, and repeated interrupts to verify a narrow
    resume-path invariant.
    - `app/tests.rs` had started accumulating unrelated startup and summary
    coverage in one flat module even after the surrounding app code was
    split into feature modules.
    
    This keeps those behaviors covered while making the tests cheaper to
    understand and less likely to rot. It also preserves the malformed-rules
    regression from #8803 without requiring a terminal orchestration test.
    
    ## What changed
    
    - Replaced the malformed `rules` startup PTY case with a direct
    exec-policy loader regression:
    
    [`rules_path_file_returns_read_dir_error`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/core/src/exec_policy_tests.rs#L264-L284)
    - Made the existing fresh-session-only startup tooltip behavior explicit
    with
    
    [`should_prepare_startup_tooltip_override`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/tui/src/app/thread_routing.rs#L1272-L1279),
    then added focused coverage for the resume/fork gate and the persisted
    NUX counter.
    - Split startup and session-summary coverage out of
    `tui/src/app/tests.rs` into dedicated modules so the test layout better
    mirrors the current app architecture.
    - Converted one single-message goal validation snapshot into semantic
    assertions where layout was not the behavior under test.
    - Removed the two PTY-heavy suite files that the narrower tests now
    supersede.
    
    ## Verification
    
    - `cargo test -p codex-core rules_path_file_returns_read_dir_error`
    - `cargo test -p codex-tui startup_`
    - `cargo test -p codex-tui session_summary_`
    - `cargo test -p codex-tui
    goal_slash_command_rejects_oversized_objective`
  • 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`
  • 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`
  • chore(config) rm Feature::CodexGitCommit (#22412)
    ## Summary
    Removes the unused Feature::CodexGitCommit
    
    ## Testing
    - [x] tests pass
  • 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.
  • feat: memories ext (#22498)
    First memories extension implementation
    Based on memories-mcp tools
  • feat: add thread lifecycle contributor hooks (#22476)
    ## Why
    
    Extensions that need thread-scoped state currently only get a start-time
    callback. That is enough for seeding stores, but it leaves the host
    without a shared extension seam for later thread rehydrate and flush
    work as thread ownership evolves. This PR turns that start-only seam
    into a host-owned thread lifecycle contributor contract so
    extension-private state can stay behind the extension API instead of
    leaking extra orchestration through core.
    
    ## What changed
    
    - Replaced `ThreadStartContributor` with `ThreadLifecycleContributor`
    and added typed lifecycle inputs for thread start, resume, and stop. The
    contract lives in
    [`contributors/thread_lifecycle.rs`](https://github.com/openai/codex/blob/d0e9211f70e58d6b07ef07e84f359d1b9aa25955/codex-rs/ext/extension-api/src/contributors/thread_lifecycle.rs#L1-L64).
    - Kept the existing start-time behavior intact by routing session
    construction through `on_thread_start`.
    - Invoked `on_thread_stop` during session shutdown before thread-scoped
    extension state is dropped, while isolating contributor failures behind
    warning logs.
    - Migrated `git-attribution` and `guardian` onto the lifecycle
    registration path.
    - Renamed the extension registry plumbing from start-specific
    contributors to lifecycle-specific contributors.
    
    ## Notes
    
    `on_thread_resume` is introduced at the API boundary here so extensions
    can target the final lifecycle shape; host resume dispatch can be wired
    where that runtime path is finalized.
  • Refactor extension tools onto shared ToolExecutor (#22369)
    ## Why
    
    Extension tools were split across two public runtime contracts:
    `codex-tool-api` exposed `ToolBundle` plus its own call/spec/error
    types, while core native tools used `codex_tools::ToolExecutor`. That
    made contributed tool specs and execution behavior easy to drift apart
    and added another crate boundary for what should be one executable-tool
    seam.
    
    This PR makes `ToolExecutor` the single runtime contract and keeps
    extension-specific pinning in `codex-extension-api`.
    
    ## Remaining todo
    
    https://github.com/openai/codex/pull/22369/changes#diff-b935ea8245c3ce568a30cff660175fa6390b66b872ae409e1e2e965738250741R5
    Either generic `Invocation` or sub-extract the `ToolCall` and clean
    `ToolInvocation`
    
    ## What changed
    
    - Removed the `codex-tool-api` workspace crate and its dependencies from
    core and `codex-extension-api`.
    - Made `codex_tools::ToolExecutor` object-safe with `async_trait` so
    extension contributors can return a dyn executor.
    - Added the extension-facing aliases under
    `ext/extension-api/src/contributors/tools.rs`, including
    `ExtensionToolExecutor = dyn ToolExecutor<ToolCall, Output =
    ExtensionToolOutput>`.
    - Changed `ToolContributor::tools` to return extension executors
    directly instead of `ToolBundle`s.
    - Updated core’s extension tool handler/registry/router path to adapt
    those extension executors into the existing native `ToolInvocation`
    runtime path.
    - Added focused coverage for extension tools being registered,
    model-visible, dispatchable, and not replacing built-in tools.
    
    ## Verification
    
    - `cargo test -p codex-tools`
    - `cargo test -p codex-extension-api`
  • feat: extract shared tool executor interface (#22359)
    ## Why
    
    Codex still models model-visible tools and executable behavior largely
    inside `codex-core`, which makes it harder to evolve the tool system
    toward a single reusable abstraction for built-ins, MCP-backed tools,
    dynamic tools, and later tools injected from outside core.
    
    This PR takes the next incremental step in that direction by moving the
    common execution-facing pieces out of core and separating them from
    core-only orchestration. The intent is to let shared tool abstractions
    improve in one place, while `codex-core` keeps the parts that are still
    inherently host-specific today, such as `ToolInvocation`, dispatch
    wiring, and hook integration.
    
    This PR is mostly moving things around. The only interesting piece is
    this abstraction:
    https://github.com/openai/codex/pull/22359/changes#diff-81af519002548ba51ed102bdaaf77e081d40a1e73a6e5f9b104bbbc96a6f1b3dR13
    
    ## What changed
    
    - Added `codex_tools::ToolExecutor<Invocation>` as the shared execution
    trait for model-visible tools.
    - Moved the reusable execution support types from `codex-core` into
    `codex-tools`:
      - `FunctionCallError`
      - `ToolPayload`
      - `ToolOutput`
    - Refactored core tool implementations so that execution behavior lives
    on `ToolExecutor<ToolInvocation>`, while `ToolHandler` remains the
    core-local extension point for hook payloads, telemetry tags, diff
    consumers, and other orchestration concerns.
    - Kept the registry and dispatch flow behaviorally unchanged while
    making the shared/extracted boundary explicit across built-in, MCP,
    dynamic, extension-backed, shell, and multi-agent tool handlers.
    
    ## Verification
    
    - `cargo test -p codex-tools`
    - `just fix -p codex-tools`
    - `just fix -p codex-core`
    - `cargo test -p codex-core` progressed through the updated tool
    surfaces and then hit the existing unrelated multi-agent stack overflow
    in
    `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`.
  • Add support for UDS in codex --remote (#22414)
    ## Why
    
    Added support for UDS connections in `codex --remote`.
    
    TUI also now connects to local app-server using UDS by default if it is
    running and set to listen to UDS connection.
    
    ## What Changed
    
    - Introduced `RemoteAppServerEndpoint` with `WebSocket` and `UnixSocket`
    variants.
    - Reused the existing JSON-RPC-over-WebSocket protocol over either a TCP
    WebSocket stream or a UDS stream.
    - Updated `codex --remote` to accept `ws://host:port`,
    `wss://host:port`, `unix://`, and `unix://PATH`.
    - Kept `--remote-auth-token-env` restricted to `wss://` and loopback
    `ws://` remotes.
    - Added a fast TUI startup probe for the default daemon socket, falling
    back to the embedded app server when the daemon is absent or
    unresponsive.
    
    ## Verification
    
    - Manually verified that the updated remote flow works.
    - Added coverage for UDS remote round trips, WebSocket auth headers,
    auth-token transport policy, remote address parsing, and missing-daemon
    fallback.
    - Ran focused remote test coverage locally.
  • Remove CODEX_RS_SSE_FIXTURE test hook (#22413)
    ## Why
    
    `CODEX_RS_SSE_FIXTURE` let integration-style CLI, exec, and TUI tests
    bypass the normal Responses transport by reading SSE from local files.
    That kept test-only behavior wired through production client code. The
    affected tests can stay hermetic by using the existing
    `core_test_support::responses` mock server and passing `openai_base_url`
    instead.
    
    ## What Changed
    
    - Removed the `CODEX_RS_SSE_FIXTURE` flag,
    `codex_api::stream_from_fixture`, the `env-flags` dependency, and the
    checked-in SSE fixture files.
    - Repointed the affected core, exec, and TUI tests at `MockServer` with
    the existing SSE event constructors.
    - Removed the Bazel test data plumbing for the deleted fixtures and
    refreshed cargo/Bazel lock state.
    
    ## Verification
    
    - `cargo build -p codex-cli`
    - `cargo test -p codex-api`
    - `cargo test -p codex-core --test all responses_api_stream_cli`
    - `cargo test -p codex-core --test all
    integration_creates_and_checks_session_file`
    - `cargo test -p codex-exec --test all ephemeral`
    - `cargo test -p codex-exec --test all resume`
    - `cargo test -p codex-tui --test all
    resume_startup_does_not_consume_model_availability_nux_count`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-api -p codex-core -p codex-exec -p codex-tui`
    - `git diff --check`
  • Restore app-server websocket listener with auth guard (#22404)
    ## Why
    PR #21843 removed the TCP websocket app-server listener, but that also
    removed functionality that still needs to exist. Restoring it as-is
    would reopen the old remote exposure problem, so this keeps the restored
    listener while making remote and non-loopback usage require explicit
    auth.
    
    ## What Changed
    - Mostly reverts #21843 and reapplies the small merge-conflict
    resolutions needed on top of current main.
    - Restores ws://IP:PORT parsing, the app-server TCP websocket acceptor,
    websocket auth CLI flags, and the associated tests.
    - The only intentional behavior change from the restored code is that
    non-loopback websocket listeners now fail startup unless --ws-auth
    capability-token or --ws-auth signed-bearer-token is configured.
    Loopback listeners remain available for local and SSH-forwarding
    workflows.
    
    ## Reviewer Focus
    Please focus review on the small auth-enforcement delta layered on top
    of the revert:
    
    - codex-rs/app-server-transport/src/transport/websocket.rs:
    start_websocket_acceptor now rejects unauthenticated non-loopback
    websocket binds before accepting connections.
    - codex-rs/app-server-transport/src/transport/auth.rs: helper logic
    classifies unauthenticated non-loopback listeners.
    - codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs:
    tests cover unauthenticated ws://0.0.0.0 startup rejection and
    authenticated non-loopback capability-token startup.
    
    Everything else is intended to be revert/merge-conflict restoration
    rather than new product behavior.
    
    ## Verification
    
    - Manually verified that TUI remoting is restored and that auth is
    enforced for non-localhost urls.
  • Unify thread metadata updates above store (#22236)
    - make ThreadStore::update_thread_metadata accept a broad range of
    metadata patches
    - keep ThreadStore::append_items as raw canonical history append (no
    metadata side effects)
    - in the local store, write these metadata updates to a combination of
    sqlite and rollout jsonl files for backwards-compat. It special cases
    which fields need to go into jsonl vs sqlite vs whatever, confining the
    awkwardness to just this implementation
    - in remote stores we can simply persist the metadata directly to a
    database, no special casing required.
    - move the "implicit metadata updates triggered by appending rollout
    items" from the RolloutRecorder (which is local-threadstore-specific) to
    the LiveThread layer above the ThreadStore, inside of a private helper
    utility called ThreadMetadataSync. LiveThread calls ThreadStore
    append_items and update_metadata separately.
    - Add a generic update metadata method to ThreadManager that works on
    both live threads and "cold" threads
    - Call that ThreadManager method from app server code, so app server
    doesn't need to worry about whether the thread is live or not
  • feat(exec-server): use protobuf relay frames (#22343)
    ## Why
    
    Remote exec-server now needs one executor websocket to serve multiple
    harness JSON-RPC sessions. Rendezvous routes by `stream_id`, and the
    exec-server side needs to use the same stable relay frame contract
    instead of a hand-rolled JSON shape.
    
    The relay protocol also needs to make ownership boundaries clear:
    harness and executor endpoints own sequencing, acks, retries, duplicate
    suppression, segmentation, and reassembly; rendezvous only routes
    frames.
    
    ## What Changed
    
    - Add the checked-in `codex.exec_server.relay.v1.RelayMessageFrame`
    proto plus generated prost bindings for `codex-exec-server`.
    - Encode remote harness/executor relay traffic as binary protobuf
    websocket frames while keeping local websocket JSON-RPC unchanged.
    - Demux executor-side relay streams into independent
    `ConnectionProcessor` sessions keyed by `stream_id`.
    - Add a programmatic `RemoteExecutorConfig::with_bearer_token(...)`
    constructor for non-CLI callers and integration tests.
    - Add an integration test that starts the remote executor against a fake
    registry/rendezvous websocket and verifies two virtual streams share one
    executor websocket without cross-talk, including per-stream reset
    behavior.
    - Document the remote relay envelope, sequence ranges, `ack`/`ack_bits`,
    and endpoint responsibilities in `exec-server/README.md`.
    
    ## Verification
    
    - `cargo test -p codex-exec-server --test relay
    multiplexed_remote_executor_routes_independent_virtual_streams --
    --exact`
    - `cargo test -p codex-exec-server --test relay`
    - `cargo test -p codex-exec-server` passed outside the sandbox. The
    sandboxed run hit macOS `sandbox-exec: sandbox_apply: Operation not
    permitted` in filesystem sandbox tests.
  • tools: remove is_mutating dispatch gating (#22382)
    ## Why
    
    Tool dispatch had two serialization mechanisms:
    
    - `supports_parallel_tool_calls` decides whether a tool participates in
    the shared parallel-execution lock.
    - `is_mutating` separately gated some calls inside dispatch.
    
    That second hook no longer carried its weight. The remaining
    parallel-support flag is already the per-tool concurrency policy, so
    keeping a second mutating gate made dispatch harder to follow and left
    behind extra session plumbing that only existed for that path.
    
    ## What changed
    
    - Removed `is_mutating` from tool handlers and deleted the
    `tool_call_gate` path that existed only to support it.
    - Simplified dispatch and routing to rely on the existing per-tool
    `supports_parallel_tool_calls` boolean.
    - Dropped the now-unused handler overrides and related session/test
    scaffolding.
    - Kept the router/parallel tests focused on the surviving per-tool
    behavior.
    - Removed the unused `codex-utils-readiness` dependency from
    `codex-core` as a follow-up fix for `cargo shear`.
    
    ## Testing
    
    - `cargo test -p codex-core
    parallel_support_does_not_match_namespaced_local_tool_names`
    - `cargo test -p codex-core mcp_parallel_support_uses_handler_data`
    - `cargo test -p codex-core
    tools_without_handlers_do_not_support_parallel`
  • feat(tui): add ambient terminal pets (#21206)
    ## Why
    
    The Codex App has animated pets, but the TUI had no equivalent ambient
    companion surface. This brings that experience into terminal Codex while
    keeping the main chat flow usable: the pet should feel present, but it
    cannot cover transcript text, composer input, approvals, or picker
    content.
    
    The feature also needs to be terminal-aware. Different terminals support
    different image protocols, tmux can interfere with image rendering, and
    some users will want pets disabled entirely or anchored differently
    depending on their layout.
    
    <table>
    <tr><td>
    <img width="4110" height="2584" alt="CleanShot 2026-05-05 at 12 41
    45@2x"
    src="https://github.com/user-attachments/assets/68a1fcbc-2104-48d6-b834-69c6aaa95cdf"
    />
    <p align="center">macOS - Ghostty, iTerm2 and WezTerm with Custom
    Pet</p>
    </td></tr>
    <tr><td>
    ![Uploading CleanShot 2026-05-10 at 20.28.30.png…]()
    <p align="center">Windows Terminal</p>
    </td></tr>
    <tr><td>
    <img width="3902" height="2752" alt="CleanShot 2026-05-05 at 12 39
    02@2x"
    src="https://github.com/user-attachments/assets/300e2931-6b00-467e-91cb-ab8e28470500"
    />
    <p align="center">Linux - WezTerm and Ghostty</p>
    </td></tr>
    </table>
    
    ## What Changed
    
    - Add a TUI ambient pet renderer in `codex-rs/tui/src/pets/`.
    - Port the app-style pet animation states so the sprite changes with
    task status, waiting-for-input states, review/ready states, and
    failures.
    - Add `/pets` selection UI with a preview pane, loading state, built-in
    pet choices, and a first-row `Disable terminal pets` option.
    - Download built-in pet spritesheets on demand from the same public CDN
    path already used by Android, under
    `https://persistent.oaistatic.com/codex/pets/v1/...`, and cache them
    locally under `~/.codex/cache/tui-pets/`.
    - Keep custom pets local.
    - Add config support for pet selection, disabling pets, and choosing
    whether the pet follows the composer bottom or anchors to the terminal
    bottom.
    - Reserve layout space around the pet so transcript wrapping, live
    responses, and composer input do not render underneath the sprite.
    - Gate image rendering by terminal capability, disable image pets under
    tmux, and support both Kitty Graphics and SIXEL terminals.
    - Add redraw cleanup for terminal image artifacts, including sixel cell
    clearing.
    
    ## Current Scope
    
    - This is an initial TUI version of ambient pets, not full App parity.
    - It focuses on ambient sprite rendering, `/pets` selection, custom
    pets, terminal capability gating, and on-demand CDN-backed built-in
    assets.
    - The ambient text overlay is currently disabled, so the TUI renders the
    pet sprite without extra status text beside it.
    
    ## How to Test
    
    1. Start Codex TUI in a terminal with image support.
    2. Run `/pets`.
    3. Confirm the picker shows built-in pets plus custom pets, and the
    first item is `Disable terminal pets`.
    4. On a fresh `~/.codex/cache/tui-pets/`, move onto a built-in pet and
    confirm the first preview downloads the spritesheet from the shared
    Codex pets CDN and renders successfully.
    5. Move through the pet list and confirm subsequent built-in previews
    use the local cache.
    6. Select a pet, then send and receive messages. Confirm transcript and
    composer text wrap before the pet instead of rendering underneath the
    sprite.
    7. Change the pet anchor setting and confirm the pet can either follow
    the composer bottom or sit at the terminal bottom.
    8. Return to `/pets`, choose `Disable terminal pets`, and confirm the
    sprite disappears cleanly.
    
    Targeted tests:
    - `cargo test -p codex-tui ambient_pet_`
    - `cargo test -p codex-tui
    resize_reflow_wraps_transcript_early_when_pet_is_enabled`
    - `cargo insta pending-snapshots`
  • [rollout-trace] Add x-codex-inference-call-id header to inference calls. (#22311)
    This allows us to attach call logs to inference requests in traces.
  • feat: guardian as an extension (contributors part) (#22216)
    Part 1 of guardian as extension. This bind all the logic to spawn
    another agent from an extension and it adds `ThreadId` in the start
    thread collaborator
  • [exec-server] serve websocket listener via HTTP upgrade (#21963)
    ## Why
    
    `codex exec-server` should keep the existing public `ws://IP:PORT` URL
    shape while serving that websocket connection through an HTTP upgrade
    path internally. That keeps the client-facing configuration simple and
    allows the listener to work through intermediate HTTP-aware
    infrastructure.
    
    ## What changed
    
    - keep the emitted and configured exec-server URL as `ws://IP:PORT`
    - serve that websocket endpoint through Axum HTTP upgrade handling on
    `/`
    - expose `GET /readyz` from the same listener for readiness checks
    - route upgraded Axum websocket streams through the shared JSON-RPC
    connection machinery
    - initialize the rustls crypto provider before websocket client
    connections
    - preserve inbound binary websocket JSON-RPC parsing for compatibility
    with the prior transport behavior
    
    ## Verification
    
    - `cargo test -p codex-exec-server --test health --test process --test
    websocket --test initialize --test exec_process`
  • daemon: refresh updater after validated binary rollout (#21853)
    ## Why
    
    `bootstrap` starts a detached pid-backed updater loop, but before this
    change that updater could keep running an old executable image even
    after `install.sh` replaced the managed standalone binary under
    `CODEX_HOME`. That left the updater itself behind the binary it had just
    rolled out, especially when the app-server was stopped or when the
    managed binary changed without a version-string change.
    
    ## What changed
    
    - Track updater identity from the executable contents rather than only
    the reported CLI version.
    - Force the managed app-server restart path when the managed binary
    contents differ from the running updater image, then re-exec the updater
    from the managed binary once the rollout is in a safe state.
    - Distinguish a genuinely absent managed app-server from a managed
    process that exists but is not yet probeable, so self-refresh does not
    skip a required restart.
    - Keep the restart/re-exec decision under the daemon operation lock so
    `bootstrap` cannot race the handoff.
    - Update `app-server-daemon/README.md` to document the resulting
    standalone and out-of-band update behavior.
    
    ## Verification
    
    - `cargo test -p codex-app-server-daemon`
    - `just fix -p codex-app-server-daemon`
    
    Added focused unit coverage for:
    - content-based updater refresh decisions
    - safe updater re-exec outcomes across restart states
  • chore: drop built-in MCPs (#22173)
    Drop something that was never used
  • app-server: remove TCP websocket listener (#21843)
    ## Why
    
    The app-server no longer needs to expose a TCP websocket listener.
    Keeping that transport also kept around a separate listener/auth surface
    that is unnecessary now that local clients can use stdio or the
    Unix-domain control socket, while remote connectivity is handled by
    `remote_control`.
    
    ## What Changed
    
    - Removed `ws://IP:PORT` parsing and the `AppServerTransport::WebSocket`
    startup path.
    - Deleted the app-server websocket listener auth module and removed
    related CLI flags/dependencies.
    - Kept websocket framing only where it is still needed: over the
    Unix-domain control socket and in the outbound `remote_control`
    connection.
    - Updated app-server CLI/help text and `app-server/README.md` to
    document only `stdio://`, `unix://`, `unix://PATH`, and `off` for local
    transports.
    - Converted affected app-server integration coverage from TCP websocket
    listeners to UDS-backed websocket connections, and added a parse test
    that rejects `ws://` listen URLs.
    - Removed the now-unused workspace `constant_time_eq` dependency and
    refreshed `Cargo.lock` after `cargo shear` caught the drift.
    - Moved test app-server UDS socket paths to short Unix temp paths so
    macOS Bazel test sandboxes do not exceed Unix socket path limits.
    
    ## Verification
    
    - Added/updated tests around UDS websocket transport behavior and
    `ws://` listen URL rejection.
    - `cargo shear`
    - `cargo metadata --no-deps --format-version 1`
    - `cargo test -p codex-app-server unix_socket_transport`
    - `cargo test -p codex-app-server unix_socket_disconnect`
    - `just fix -p codex-app-server`
    - `git diff --check`
    
    Local full Rust test execution was blocked before compilation by an
    external fetch failure for the pinned `nornagon/crossterm` git
    dependency. `just bazel-lock-update` and `just bazel-lock-check` were
    retried after the manifest cleanup but remain blocked by external
    BuildBuddy/V8 fetch timeouts.
  • feat: wire extension tool bundles into core (#22147)
    ## Why
    
    This is the next narrow step toward moving concrete tool families out of
    core. After #22138 introduced `codex-tool-api`, we still needed a real
    end-to-end seam that lets an extension own an executable tool definition
    once and have core install it without the temporary `extension-api`
    wrapper or a dependency on `codex-tools`.
    
    `codex-tool-api` is the small extension-facing execution contract, while
    `codex-tools` still has a different job: host-side shared tool metadata
    and planning logic that is not “run this contributed tool”, like spec
    shaping, namespaces, discovery, code-mode augmentation, and
    MCP/dynamic-to-Responses API conversion
    
    ## What changed
    
    - Moved the shared leaf tool-spec and JSON Schema types into
    `codex-tool-api`, so the executable contract now lives with
    [`ToolBundle`](https://github.com/openai/codex/blob/c538758095337d4fe0a52a172363ccede4066bda/codex-rs/tool-api/src/bundle.rs#L19-L70).
    - Replaced the temporary extension-side tool wrapper with direct
    `ToolBundle` use in `codex-extension-api`.
    - Taught core to collect contributed bundles, include them in spec
    planning, register them through
    [`ToolRegistryBuilder::register_tool_bundle`](https://github.com/openai/codex/blob/c538758095337d4fe0a52a172363ccede4066bda/codex-rs/core/src/tools/registry.rs#L653-L667),
    and dispatch them through the existing router/runtime path.
    - Added focused coverage for contributed tools becoming model-visible
    and dispatchable, plus spec-planning coverage for contributed function
    and freeform tools.
    
    ## Verification
    
    - Added `extension_tool_bundles_are_model_visible_and_dispatchable` in
    `core/src/tools/router_tests.rs`.
    - Added spec-plan coverage in `core/src/tools/spec_plan_tests.rs` for
    contributed extension bundles.
    
    ## Related
    
    - Follow-up to #22138
  • refactor: extract executable tool contracts into codex-tool-api (#22138)
    ## Why
    The tool-extraction work needs one shared executable-tool seam that
    hosts and tool owners can depend on without reaching into `codex-core`.
    Landing that seam first makes the later tool-family ports incremental
    and keeps the reusable contract separate from any one migration.
    
    ## What changed
    - add a new `codex-tool-api` crate and workspace wiring
    - move the common executable-tool contracts into that crate:
    `ToolBundle`, `ToolDefinition`, `ToolExecutor`, `ToolCall`, `ToolInput`,
    `ToolOutput`, `JsonToolOutput`, and `ToolError`
    - keep host state generic through `ToolBundle<C>` / `ToolCall<C>` so
    later integrations can provide their own runtime context without baking
    core types into the API
    - carry the host signals the runtime will need later, including
    parallel-call support and mutability probing
    - leave existing tool families in place for now; this PR only
    establishes the reusable API surface
    - add the Bazel target and lockfile updates for the new crate
    
    ## Testing
    - `cargo test -p codex-tool-api`
  • extension: move git attribution into an extension (#21738)
    ## Why
    
    Git commit attribution is prompt policy, not session orchestration.
    After #21737 adds the extension-registry seam, this moves that
    prompt-only behavior out of `codex-core` so `Session` can consume
    extension-contributed prompt fragments instead of owning a one-off
    policy path itself.
    
    Before this PR, `Session` injected the trailer instruction directly from
    `codex-core` ([session
    assembly](https://github.com/openai/codex/blob/a57a747eb667753118217b8bb47dfd1fff88cbde/codex-rs/core/src/session/mod.rs#L2733-L2739),
    [helper
    module](https://github.com/openai/codex/blob/a57a747eb667753118217b8bb47dfd1fff88cbde/codex-rs/core/src/commit_attribution.rs#L1-L33)).
    This branch moves that same responsibility into
    [`codex-git-attribution`](https://github.com/openai/codex/blob/b5029a67360fe5c948aa849d4cf65fd2597ebaae/codex-rs/ext/git-attribution/src/lib.rs#L14-L100).
    
    ## What changed
    
    - Added the `codex-git-attribution` extension crate.
    - Snapshot `CodexGitCommit` plus `commit_attribution` at thread start,
    then contribute the developer-policy fragment through the extension
    registry.
    - Register the extension in app-server thread extensions.
    - Remove the old `codex-core` helper module and direct `Session`
    injection path.
    
    This keeps the existing behavior intact: the prompt is only contributed
    when `CodexGitCommit` is enabled, blank attribution still disables the
    trailer, and the default remains `Codex <noreply@openai.com>`.
    
    ## Stack
    
    - Stacked on #21737.
  • extension: wire extension registries into sessions (#21737)
    ## Why
    
    [#21736](https://github.com/openai/codex/pull/21736) introduces the
    typed extension API, but the runtime does not yet carry a registry
    through thread/session startup or give contributors host-owned stores to
    read from. This PR wires that host-side path so later feature migrations
    can move product-specific behavior behind typed contributions without
    adding another bespoke seam directly to `codex-core`.
    
    ## What changed
    
    - Thread `ExtensionRegistry<Config>` through `ThreadManager`,
    `CodexSpawnArgs`, `Session`, and sub-agent spawn paths.
    - Wire `ThreadStartContributor` and `ContextContributor`
    - Expose the small supporting surface needed by non-core callers that
    construct threads directly, including `empty_extension_registry()`
    through `codex-core-api`.
    
    This PR lands the host plumbing only: the app-server registry is still
    empty, and concrete feature migrations are intended to follow
    separately.
  • extension: add initial typed extension API (#21736)
    ## Why
    
    `codex-core` still owns a growing amount of product-specific behavior.
    This PR starts the extraction path by introducing a small, typed
    first-party extension seam: features can install the contribution
    families they actually own, while the host keeps lifecycle and state
    ownership instead of pushing a broad service locator into the API.
    
    See the `examples/` for illustration
    
    ## Known limitations
    * Tool contract definition will be shared with core
    * Fragments must be extracted
    * Missing some contributors
  • Move file watcher out of core (#21290)
    ## Why
    
    The app-server watcher relocation leaves the generic filesystem watcher
    as the last watcher-specific implementation still living inside
    `codex-core`. Moving that code to a small crate keeps `codex-core`
    focused on thread execution and lets app-server depend on the watcher
    without reaching back into core for filesystem watching primitives.
    
    This PR is stacked on #21287.
    
    ## What changed
    
    - Added a new `codex-file-watcher` crate containing the existing watcher
    implementation and its unit tests.
    - Updated app-server `fs_watch`, `skills_watcher`, and listener state to
    import watcher types from `codex-file-watcher`.
    - Removed the `file_watcher` module and `notify` dependency from
    `codex-core`.
    - Updated Cargo workspace metadata and `Cargo.lock` for the new internal
    crate.
    
    ## Validation
    
    - `cargo check -p codex-file-watcher -p codex-core -p codex-app-server`
    - `cargo test -p codex-file-watcher`
    - `cargo test -p codex-app-server
    skills_changed_notification_is_emitted_after_skill_change`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just fix -p codex-file-watcher`
    - `just fix -p codex-core`
    - `just fix -p codex-app-server`
  • [daemon] Add app-server daemon lifecycle management (#20718)
    ## Why
    
    Desktop and mobile Codex clients need a machine-readable way to
    bootstrap and manage `codex app-server` on remote machines reached over
    SSH. The same flow is also useful for bringing up app-server with
    `remote_control` enabled on a fresh developer machine and keeping that
    managed install current without requiring a human session.
    
    ## What changed
    
    - add the new experimental `codex-app-server-daemon` crate and wire it
    into `codex app-server daemon` lifecycle commands: `start`, `restart`,
    `stop`, `version`, and `bootstrap`
    - add explicit `enable-remote-control` and `disable-remote-control`
    commands that persist the launch setting and restart a running managed
    daemon so the change takes effect immediately
    - emit JSON success responses for daemon commands so remote callers can
    consume them directly
    - support a Unix-only pidfile-backed detached backend for lifecycle
    management
    - assume the standalone `install.sh` layout for daemon-managed binaries
    and always launch `CODEX_HOME/packages/standalone/current/codex`
    - add bootstrap support for the standalone managed install plus a
    detached hourly updater loop
    - harden lifecycle management around concurrent operations, pidfile
    ownership, stale state cleanup, updater ownership, managed-binary
    preflight, Unix-only rejection, forced shutdown after the graceful
    window, and updater process-group tracking/cleanup
    - document the experimental Unix-only support boundary plus the
    standalone bootstrap/update flow in
    `codex-rs/app-server-daemon/README.md`
    
    ## Verification
    
    - `cargo test -p codex-app-server-daemon -p codex-cli`
    - live pid validation on `cb4`: `bootstrap --remote-control`, `restart`,
    `version`, `stop`
    
    ## Follow-up
    
    - Add updater self-refresh so the long-lived `pid-update-loop` can
    replace its own executable image after installing a newer managed Codex
    binary.
  • [codex] support executor registry remote environments (#21323)
    ## Summary
    
    Support registry-backed remote executors end to end so downstream
    services can resolve an executor id into an exec-server URL and make
    that environment available to Codex without relying on the legacy cloud
    environments flow.
    
    ## What changed
    
    - switch remote executor registration to the executor registry bootstrap
    contract
    - allow named remote environments to be inserted into
    `EnvironmentManager` at runtime
    - add the experimental app-server RPC `environment/add` so initialized
    experimental clients can register those remote environments for later
    `thread/start` and `turn/start` selection
    
    ## Validation
    
    Ran focused validation locally:
    - `cargo test -p codex-exec-server environment_manager_`
    - `cargo test -p codex-exec-server
    register_executor_posts_with_bearer_token_header`
    - `cargo test -p codex-app-server-protocol`
  • Using cached connector directory for discoverable tools list (#21497)
    ## Summary
    
    Startup tool construction currently depends on connector directory
    metadata for `tool_suggest` discoverables. On a cold directory cache,
    that can put slow connector-directory requests on the blocking path even
    though the tools array only needs directory data for install
    suggestions, not for the live connector MCP tools themselves.
    
    This PR keeps the discoverables path off that cold network fetch:
    - read connector directory metadata from cache only when building
    discoverable tools
    - persist connector directory metadata to
    `~/.codex/cache/codex_app_directory/<hash>.json` and use it to hydrate
    the in-memory cache on later runs before the normal refresh path updates
    it
    - use connector-directory-specific cache naming to distinguish this
    metadata cache from the separate Codex Apps tools-spec cache
    
    This reduces first-turn startup work without changing how live connector
    MCP tools are sourced. Longer term, directory-backed install suggestions
    should move to a search-based flow so they no longer need to be inlined
    into the tools prompt at all.
    
    ## Testing
    
    - `cargo test -p codex-connectors`
    - `cargo test -p codex-chatgpt`
    - `cargo test -p codex-core
    request_plugin_install_is_available_without_search_tool_after_discovery_attempts`
    - `cargo test -p codex-core
    tool_suggest_uses_connector_id_fallback_when_directory_cache_is_empty`
  • Enable --deny-warnings for cargo shear (#21616)
    ## Summary
    
    In https://github.com/openai/codex/pull/21584, we disabled doctests for
    crates that lack any doctests. We can enforce that property via `cargo
    shear --deny-warnings`: crates that lack doctests will be flagged if
    doctests are enabled, and crates with doctests will be flagged if
    doctests are disabled.
    
    A few additional notes:
    
    - By adding `--deny-warnings`, `cargo shear` also flagged a number of
    modules that were not reachable at all. Some of those have been removed.
    - This PR removes a usage of `windows_modules!` (since `cargo shear` and
    `rustfmt` couldn't see through it) in favor of simple `#[cfg(target_os =
    "windows")]` macros. As a consequence, many of these files exhibit churn
    in this PR, since they weren't being formatted by `rustfmt` at all on
    main.
    - Again, to make the code more analyzable, this PR also removes some
    usages of `#[path = "cwd_junction.rs"]` in favor of a more standard
    module structure. The bin sidecar structure is still retained, but,
    e.g., `windows-sandbox-rs/src/bin/command_runner.rs‎` was moved to
    `windows-sandbox-rs/src/bin/command_runner/main.rs`, and so on.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: enable AWS login credentials for Bedrock auth (#21623)
    ## Summary
    
    Codex's Amazon Bedrock provider signs Mantle requests with SigV4 using
    credentials resolved by the AWS SDK. That worked for standard AWS
    profiles and environment credentials, but AWS CLI console-login profiles
    created by `aws login` require the SDK's `credentials-login` feature to
    resolve `login_session` credentials.
    
    This change enables that credential provider so Bedrock can use AWS
    console-login credentials through the existing provider-owned AWS auth
    path.
    
    While testing the console-login path, we also hit a Mantle-specific
    SigV4 regression from the new split between `session_id` and
    `thread_id`. Mantle does not preserve legacy OpenAI compatibility
    headers that use `snake_case` before SigV4 verification, so signing
    those headers can make the server reconstruct a different canonical
    request. The Bedrock auth path now removes that header class before
    signing, keeping preserved hyphenated Codex/AWS headers such as
    `x-codex-turn-metadata` signed normally.
    
    ## Changes
    
    - Enable `aws-config`'s `credentials-login` feature in
    `codex-rs/aws-auth`.
    - Add a compile-time regression test for
    `aws_config::login::LoginCredentialsProvider`.
    - Strip `snake_case` compatibility headers from Bedrock Mantle SigV4
    requests before signing.
    - Expand the Bedrock auth regression test to cover `session_id`,
    `thread_id`, and future headers of the same shape.
    - Refresh Cargo and Bazel lockfiles for the added `aws-sdk-signin`
    dependency.
    
    ## Tests
    - tested with `aws login` locally and verified that it works as
    intended.
  • Add CODEX_HOME environments TOML provider (#20666)
    ## Why
    
    After stdio transports and provider-owned defaults exist, Codex needs a
    config-backed provider that can describe more than the single legacy
    `CODEX_EXEC_SERVER_URL` remote. This PR adds that provider without
    activating it in product entrypoints yet, keeping parser/validation
    review separate from runtime wiring.
    
    **Stack position:** this is PR 4 of 5. It builds on PR 3's
    provider/default model and adds the `environments.toml` provider used by
    PR 5.
    
    ## What Changed
    
    - Add `environment_toml.rs` as the TOML-specific home for parsing,
    validation, and provider construction.
    - Keep the TOML schema/provider structs private; the public constructor
    added here is `EnvironmentManager::from_codex_home(...)`.
    - Add `TomlEnvironmentProvider`, including validation for:
      - reserved ids such as `local` and `none`
      - duplicate ids
      - unknown explicit defaults
      - empty programs or URLs
      - exactly one of `url` or `program` per configured environment
    - Support websocket environments with `url = "ws://..."` / `wss://...`.
    - Support stdio-command environments with `program = "..."`.
    - Add helpers to load `environments.toml` from `CODEX_HOME`, but do not
    wire entrypoints to call them yet.
    - Add the `toml` dependency for parsing.
    
    ## Stack
    
    - 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server
    listener
    - 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server
    client transport
    - 3. https://github.com/openai/codex/pull/20665 - Make environment
    providers own default selection
    - **4. This PR:** https://github.com/openai/codex/pull/20666 - Add
    CODEX_HOME environments TOML provider
    - 5. https://github.com/openai/codex/pull/20667 - Load configured
    environments from CODEX_HOME
    
    Split from original draft: https://github.com/openai/codex/pull/20508
    
    ## Validation
    
    Not run locally; this was split out of the original draft stack.
    
    ## Documentation
    
    This introduces the config shape for `environments.toml`; user-facing
    documentation should be added before this stack is treated as a
    documented public workflow.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Remove remote thread store implementation (#21596)
    Remove the remote thread-store backend and checked-in protobuf
    artifacts. We've moved these into another crate that link against this
    one.
    
    Also remove the config settings for thread store backend selection,
    since we'll instead pass an instantiated thread store into the core-api
    crate's main entrypoint.
  • feat: make built-in MCPs first-class runtime servers (#21356)
    ## DISCLAIMER
    This is experimental and no production service must rely on this
    
    ## Why
    
    Built-in MCPs are product-owned runtime capabilities, but they were
    previously flattened into the same config-backed stdio path as
    user-configured servers. That made them depend on a hidden `codex
    builtin-mcp` re-exec path, exposed them through config-oriented CLI
    flows, and erased distinctions the runtime needs to preserve—most
    notably whether an MCP call should count as external context for
    memory-mode pollution.
    
    ## What changed
    
    - Model product-owned built-ins separately from config-backed MCP
    servers via `BuiltinMcpServer` and `EffectiveMcpServer`.
    - Launch built-ins in process through a reusable async transport instead
    of the hidden `builtin-mcp` stdio subcommand.
    - Keep config-oriented CLI operations such as `codex mcp
    list/get/login/logout` scoped to configured servers, while merging
    built-ins only into the effective runtime server set.
    - Retain server metadata after launch so parallel-tool support and
    context classification come from the live server set; built-in
    `memories` is now classified as local Codex state rather than external
    context.
    
    ## Test plan
    
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core --test suite
    builtin_memories_mcp_call_does_not_mark_thread_memory_mode_polluted_when_configured`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Show plugin hooks in plugin details (#21447)
    Supersedes the abandoned #19859, rebuilt on latest `main`.
    
    # Why
    
    PR #19705 adds discovery for hooks bundled with plugins, but `/plugins`
    still only shows skills, apps, and MCP servers. This follow-up makes
    bundled hooks visible in the same plugin detail view so users can
    inspect the full plugin surface in one place.
    
    We also need `PluginHookSummary` to populate Plugin Hooks in the app;
    `hooks/list` is not enough there because plugin detail needs to show
    hooks for disabled plugins too.
    
    # What
    
    - extend `plugin/read` with `PluginHookSummary` entries for bundled
    hooks
    - summarize plugin hooks while loading plugin details
    - render a `Hooks` row in the `/plugins` detail popup
    
    <img width="3456" height="848" alt="CleanShot 2026-04-27 at 11 45 34@2x"
    src="https://github.com/user-attachments/assets/fe3a38d6-a260-4351-8513-fb04c93d725b"
    />
  • Revert state DB injection and agent graph store (#21481)
    ## Why
    
    Reverts #20689 to restore the previous optional state DB plumbing. The
    conflict resolution keeps the newer installation ID and session/thread
    identity changes that landed after #20689, while removing the mandatory
    state DB and agent graph store dependency from ThreadManager
    construction.
    
    ## What changed
    
    - Restored `Option<StateDbHandle>` through app-server, MCP server,
    prompt debug, and test entry points.
    - Removed the `codex-core` dependency on `codex-agent-graph-store` and
    reverted descendant lookup back to the existing state DB path when
    available.
    - Kept newer `installation_id` forwarding by passing it beside the
    optional DB handle.
    - Kept local thread-name updates working when the optional state DB
    handle is absent.
    
    ## Validation
    
    - `git diff --check`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-state -p codex-rollout -p
    codex-app-server-protocol`
    - Attempted `env CARGO_INCREMENTAL=0 cargo test -p codex-core -p
    codex-app-server -p codex-app-server-client -p codex-mcp-server -p
    codex-thread-manager-sample -p codex-tui`; blocked locally by a rustc
    ICE while compiling `v8 v146.4.0` with `rustc 1.93.0 (254b59607
    2026-01-19)` on `aarch64-apple-darwin`.
  • Remove core MCP list tools op (#21281)
    ## Why
    
    The core `Op::ListMcpTools` request path is no longer needed. Keeping it
    around left a dead request/response surface alongside the app-server MCP
    inventory APIs that own current server status listing.
    
    ## What Changed
    
    - Removed `Op::ListMcpTools`, `EventMsg::McpListToolsResponse`, and the
    core handler that built the MCP snapshot response.
    - Removed the now-unused `codex-mcp` snapshot wrapper/export and passive
    event handling arms in rollout and MCP-server consumers.
    - Updated tests that used the old op as a synchronization hook to wait
    on existing startup/skills events, and deleted the plugin test that only
    exercised the removed listing op.
    
    ## Validation
    
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-rollout -p codex-rollout-trace -p
    codex-mcp-server`
    - `cargo test -p codex-core --test all
    pending_input::queued_inter_agent_mail`
    - `cargo test -p codex-core --test all
    rmcp_client::stdio_mcp_tool_call_includes_sandbox_state_meta`
    - `cargo test -p codex-core --test all
    rmcp_client::stdio_image_responses`
    - `just fix -p codex-core -p codex-protocol -p codex-mcp -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server`
  • Move message history out of core (#21278)
    ## Why
    
    Message history was implemented inside `codex-core` and surfaced through
    core protocol ops and `SessionConfiguredEvent` fields even though the
    current consumer is TUI-local prompt recall. That made core own UI
    history persistence and exposed `history_log_id` / `history_entry_count`
    through surfaces that app-server and other clients do not need.
    
    This change moves message history persistence out of core and keeps the
    recall plumbing local to the TUI.
    
    ## What changed
    
    - Added a new `codex-message-history` crate for appending, looking up,
    trimming, and reading metadata from `history.jsonl`.
    - Removed core protocol history ops/events: `AddToHistory`,
    `GetHistoryEntryRequest`, and `GetHistoryEntryResponse`.
    - Removed `history_log_id` and `history_entry_count` from
    `SessionConfiguredEvent` and updated exec/MCP/test fixtures accordingly.
    - Updated the TUI to dispatch local app events for message-history
    append/lookup and keep its persistent-history metadata in TUI session
    state.
    
    ## Validation
    
    - `cargo test -p codex-message-history -p codex-protocol`
    - `cargo test -p codex-exec event_processor_with_json_output`
    - `cargo test -p codex-mcp-server outgoing_message`
    - `cargo test -p codex-tui`
    - `just fix -p codex-message-history -p codex-protocol -p codex-core -p
    codex-tui -p codex-exec -p codex-mcp-server`
  • test: isolate app-server-client in-process test state (#21328)
    ## Why
    
    The in-process `app-server-client` tests were still building their
    configs from the ambient `codex_home` and letting the embedded app
    server create its own state DB when `state_db` was absent. That matters
    because in-process startup falls back to
    `init_state_db_from_config(...)` in that case, so tests can otherwise
    share persisted state instead of getting isolated fixtures:
    [`app-server/src/in_process.rs`](https://github.com/openai/codex/blob/a98623511ba433154ec811fc63091617f5945438/codex-rs/app-server/src/in_process.rs#L368-L373).
    
    ## What changed
    
    - Give each in-process test client its own temporary `codex_home`.
    - Initialize the matching state DB from that per-client config and pass
    it into the client explicitly.
    - Keep the temp directory alive for the lifetime of the test client
    through a small `TestClient` wrapper.
    - Add `tempfile` as a dev dependency for the new harness.
    
    The updated setup lives in
    [`app-server-client/src/lib.rs`](https://github.com/openai/codex/blob/35c1133d45d10931914dbb88a1246a195d025ff6/codex-rs/app-server-client/src/lib.rs#L982-L1055).
    
    ## Testing
    
    - Existing `codex-app-server-client` tests continue to exercise the
    updated in-process client path through the isolated helper.
  • linux-sandbox: use standalone bundled bwrap (#21255)
    **Summary**
    - Add `codex-bwrap`, a standalone `bwrap` binary built from the existing
    vendored bubblewrap sources.
    - Remove the linked vendored bwrap path from `codex-linux-sandbox`;
    runtime now prefers system `bwrap` and falls back to bundled
    `codex-resources/bwrap`.
    - Add bundled SHA-256 verification with missing/all-zero digest as the
    dev-mode skip value, then exec the verified file through
    `/proc/self/fd`.
    - Keep `launcher.rs` focused on choosing and dispatching the preferred
    launcher. Bundled lookup, digest verification, and bundled exec now live
    in `linux-sandbox/src/bundled_bwrap.rs`; Bazel runfiles lookup lives in
    `linux-sandbox/src/bazel_bwrap.rs`; shared argv/fd exec helpers live in
    `linux-sandbox/src/exec_util.rs`.
    - Teach Bazel tests to surface the Bazel-built `//codex-rs/bwrap:bwrap`
    through `CARGO_BIN_EXE_bwrap`; `codex-linux-sandbox` only honors that
    fallback in debug Bazel runfiles environments so release/user runtime
    lookup stays tied to `codex-resources/bwrap`.
    - Allow `codex-exec-server` filesystem helpers to preserve just the
    Bazel bwrap/runfiles variables they need in debug Bazel builds, since
    those helpers intentionally rebuild a small environment before spawning
    `codex-linux-sandbox`.
    - Verify the Bazel bwrap target in Linux release CI with a build-only
    check. Running `bwrap --version` is too strong for GitHub runners
    because bubblewrap still attempts namespace setup there.
    
    **Verification**
    - Latest update: `cargo test -p codex-linux-sandbox`
    - Latest update: `just fix -p codex-linux-sandbox`
    - `cargo check --target x86_64-unknown-linux-gnu -p codex-linux-sandbox`
    could not run locally because this macOS machine does not have
    `x86_64-linux-gnu-gcc`; GitHub Linux Bazel CI is expected to cover the
    Linux-only modules.
    - Earlier in this PR: `cargo test -p codex-bwrap`
    - Earlier in this PR: `cargo test -p codex-exec-server`
    - Earlier in this PR: `cargo check --release -p codex-exec-server`
    - Earlier in this PR: `just fix -p codex-linux-sandbox -p
    codex-exec-server`
    - Earlier in this PR: `bazel test --nobuild
    //codex-rs/linux-sandbox:linux-sandbox-all-test
    //codex-rs/core:core-all-test
    //codex-rs/exec-server:exec-server-file_system-test
    //codex-rs/app-server:app-server-all-test` (analysis completed; Bazel
    then refuses to run tests under `--nobuild`)
    - Earlier in this PR: `bazel build --nobuild //codex-rs/bwrap:bwrap`
    - Prior to this update: `just bazel-lock-update`, `just
    bazel-lock-check`, and YAML parse check for
    `.github/workflows/bazel.yml`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21255).
    * #21257
    * #21256
    * __->__ #21255
  • Add cloud executor registration to exec-server (#19575)
    ## Summary
    This PR adds the first `codex-rs` milestone for remote-exec e2e: a local
    `codex exec-server` can now register itself with
    `codex-cloud-environments` and attach to the returned rendezvous
    websocket.
    
    At a high level, `codex exec-server --cloud ...` now:
    - loads ChatGPT auth from normal Codex config
    - registers an executor with `codex-cloud-environments`
    - receives a signed rendezvous websocket URL
    - serves the existing exec-server JSON-RPC protocol over that websocket
    
    ## What Changed
    - Added `--cloud`, `--cloud-base-url`, `--cloud-environment-id`, and
    `--cloud-name` to `codex exec-server`
    - Added a new `exec-server/src/cloud.rs` module that handles:
      - registration requests
      - auth/header setup
      - bounded auth retry on `401/403`
      - reconnect/backoff after websocket disconnects
    - Reused the existing `ConnectionProcessor` / `ExecServerHandler` path
    so cloud mode serves the same exec/filesystem RPC surface as local
    websocket mode
    - Added cloud-specific error variants and minimal docs for the new mode
    
    ## Testing
    Manual e2e test that fully goes through exec server flow with our codex
    cloud agent as orchestrator