Commit Graph

2885 Commits

  • feat: add layered --profile-v2 config files (#17141)
    ## Why
    
    `--profile-v2 <name>` gives launchers and runtime entry points a named
    profile config without making each profile duplicate the base user
    config. The base `$CODEX_HOME/config.toml` still loads first, then
    `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
    writable user config for that session.
    
    That keeps shared defaults, plugin/MCP setup, and managed/user
    constraints in one place while letting a named profile override only the
    pieces that need to differ.
    
    ## What Changed
    
    - Added the shared `--profile-v2 <name>` runtime option with validated
    plain names, now represented by `ProfileV2Name`.
    - Extended config layer state so the base user config and selected
    profile config are both `User` layers; APIs expose the active user layer
    and merged effective user config.
    - Threaded profile selection through runtime entry points: `codex`,
    `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
    debug prompt-input`.
    - Made user-facing config writes go to the selected profile file when
    active, including TUI/settings persistence, app-server config writes,
    and MCP/app tool approval persistence.
    - Made plugin, marketplace, MCP, hooks, and config reload paths read
    from the merged user config so base and profile layers both participate.
    - Updated app-server config layer schemas to mark profile-backed user
    layers.
    
    ## Limits
    
    `--profile-v2` is still rejected for config-management subcommands such
    as feature, MCP, and marketplace edits. Those paths remain tied to the
    base `config.toml` until they have explicit profile-selection semantics.
    
    Some adjacent background writes may still update base or global state
    rather than the selected profile:
    
    - marketplace auto-upgrade metadata
    - automatic MCP dependency installs from skills
    - remote plugin sync or uninstall config edits
    - personality migration marker/default writes
    
    ## Verification
    
    Added targeted coverage for profile name validation, layer
    ordering/merging, selected-profile writes, app-server config writes,
    session hot reload, plugin config merging, hooks/config fixture updates,
    and MCP/app approval persistence.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Wire turn item contributors into stream output (#22494)
    ## Summary
    - run registered TurnItemContributor hooks for parsed stream output
    items
    - plumb the active turn extension store into stream item handling
    - preserve existing memory citation parsing as fallback after
    contributors run
    
    ## Tests
    - cargo test -p codex-core stream_events_utils -- --nocapture
    - just fmt
    - just fix -p codex-core
    - git diff --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`
  • chore(config) rm experimental_use_freeform_apply_patch (#22565)
    ## Summary
    Get rid of the `experimental_use_freeform_apply_patch` config option,
    since it is now encoded in model config. No deprecation message since it
    has been experimental this entire time.
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Remove connector_openai prefix filtering (#22555)
    Remove unnecessary prefix filtering from codex
    
    ## Test Plan
    
    Test local cli build + make sure backend returns appropriate apps 
    
    ```
    cd ~/code/codex/codex-rs
    cargo build -p codex-cli --bin codex
    ./target/debug/codex
    ```
    
    Appropriate apps show up in my list
  • Make multi_agent_v2 wait_agent timeouts configurable (#22528)
    ## Why
    
    `multi_agent_v2` already allowed configuring the minimum `wait_agent`
    timeout, but the default timeout and upper bound were still hard-coded.
    That made it hard to tune waits for subagent mailbox activity in
    sessions that need either faster wakeups or longer waits, and it meant
    the model-visible `wait_agent` schema could not fully reflect the
    resolved runtime limits.
    
    ## What Changed
    
    - Added `features.multi_agent_v2.max_wait_timeout_ms` and
    `features.multi_agent_v2.default_wait_timeout_ms` alongside the existing
    `min_wait_timeout_ms` setting.
    - Validated all three timeouts in config as `0..=3_600_000`, with
    `min_wait_timeout_ms <= default_wait_timeout_ms <= max_wait_timeout_ms`.
    - Thread and review session tool config now passes the resolved
    min/default/max values into the `wait_agent` tool schema.
    - `wait_agent` now uses the configured default when `timeout_ms` is
    omitted and rejects explicit values outside the configured min/max range
    instead of silently clamping them.
    - Updated the generated config schema and config-lock test coverage for
    the new fields.
  • Avoid PowerShell profiles in elevated Windows sandbox (#21400)
    ## Why
    
    On Windows, elevated sandboxed commands run under a dedicated sandbox
    account while `HOME` / `USERPROFILE` can still point at the real user's
    profile directory. For PowerShell login shells, that combination can
    make the sandbox account try to load the real user's PowerShell profile
    script. If the sandbox account's execution policy differs from the real
    user's policy, startup can emit profile-loading errors before the
    requested command runs.
    
    For this backend, loading the profile is not a faithful user login
    shell: it is cross-account profile execution. Treating these PowerShell
    invocations as non-login shells avoids that invalid startup path.
    
    ## Why This Happens Late
    
    The normal `login` decision is resolved when shell argv is created, but
    that point is too early to make this Windows sandbox-specific decision.
    At argv creation time we do not yet know the actual sandbox attempt that
    will run the command. A turn can include sandboxed and unsandboxed
    attempts, and a broad turn-level override would also affect Full Access
    commands where the user's profile should remain available.
    
    Instead, this change carries the selected `ShellType` alongside the argv
    and applies the `-NoProfile` adjustment in the shell runtimes once the
    `SandboxAttempt` is known. That keeps the override scoped to actual
    `WindowsRestrictedToken` attempts with `WindowsSandboxLevel::Elevated`.
    
    The runtime uses the selected shell metadata rather than re-detecting
    PowerShell from argv. That avoids brittle parsing and covers PowerShell
    invocation shapes such as `-EncodedCommand`.
    
    ## What Changed
    
    - Carry selected shell metadata through `exec_command` / unified exec
    requests and shell tool requests.
    - Insert `-NoProfile` for PowerShell commands only when the runtime is
    about to execute a sandboxed elevated Windows attempt.
    - Add focused unit coverage for elevated Windows PowerShell,
    `-EncodedCommand`, existing `-NoProfile`, legacy restricted-token
    attempts, unsandboxed attempts, and non-PowerShell commands.
    
    ## Verification
    
    - `cargo test -p codex-core disable_powershell_profile_tests`
    - `cargo test -p codex-core test_get_command`
    - `cargo clippy --fix --tests --allow-dirty --allow-no-vcs -p
    codex-core`
    
    A full `cargo test -p codex-core` run was also attempted during
    development, but it still hit an unrelated stack overflow in
    `agent::control` tests before reaching this area.
  • clean up instructions (#22543)
    rm behavioral steering in tool docs for code mode.
  • Use selected environment cwd for filesystem helpers (#22542)
    ## Why
    
    `TurnContext::cwd` is deprecated in favor of resolving paths from the
    selected turn environment cwd. A few filesystem-oriented paths were
    still constructing sandbox context from the legacy cwd and then mutating
    it afterward, or resolving local file paths through the deprecated
    helper.
    
    ## What changed
    
    - Make `TurnContext::file_system_sandbox_context` take the trusted cwd
    explicitly.
    - Pass the selected turn environment cwd directly from `apply_patch` and
    `view_image` call sites.
    - Restrict `spawn_agents_on_csv` to exactly one local environment and
    resolve input/output CSV paths from that local environment cwd.
    - Remove a redundant test setup assignment that only synchronized
    deprecated `TurnContext::cwd` with a replaced config.
    
    ## Validation
    
    - `cargo test -p codex-core view_image`
    - `cargo test -p codex-core
    maybe_persist_mcp_tool_approval_writes_project_config_for_project_server`
    - `cargo test -p codex-core parse_csv_supports_quotes_and_commas`
    - `git diff --check`
  • chore(config) rm tools.view_image (#22501)
    ## Summary
    It appears this config flag has been broken/a noop for quite some time:
    since https://github.com/openai/codex/pull/8850. Let's simplify and get
    rid of this.
    
    ## Testing
    - [x] Updated unit tests
  • chore(config) rm Feature::CodexGitCommit (#22412)
    ## Summary
    Removes the unused Feature::CodexGitCommit
    
    ## Testing
    - [x] tests pass
  • [codex] Reuse Apps MCP path override for plugin-service rollout (#22527)
    ## Summary
    - reuse `apps_mcp_path_override` for the plugin-service rollout,
    defaulting enabled boolean overrides to `/ps/mcp` while preserving
    explicit configured paths
    
    ## Validation
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core apps_mcp_path_override`
    - `cargo test -p codex-core
    to_mcp_config_preserves_apps_feature_from_config`
    - `cargo test -p codex-features`
  • Deprecate TurnContext cwd and resolve_path (#22519)
    ## Why
    
    `TurnContext::cwd` and `TurnContext::resolve_path` are being phased out
    in favor of using the selected turn environment cwd directly.
    Deprecating both APIs makes any new direct dependency visible while
    preserving the existing migration path for current callers.
    
    ## What Changed
    
    - Marked `TurnContext::cwd` and `TurnContext::resolve_path` as
    deprecated with guidance to use the selected turn environment cwd
    instead.
    - Added exact `#[allow(deprecated)]` suppressions at each existing
    direct usage site, including tests, rather than adding crate-wide
    suppression.
    - Kept the change behavior-preserving: current cwd reads, writes, and
    path resolution continue to use the same values.
    
    ## Verification
    
    - `just fmt`
    - `cargo check -p codex-core`
    - `cargo check -p codex-core --tests`
    - `git diff --check`
  • feat: expose multi-agent v2 as model-only tools (#22514)
    ## Why
    
    `code_mode_only` filters code-mode nested tools out of the top-level
    tool list. For multi-agent v2, we need a rollout shape where the
    collaboration tools remain callable as normal model tools without also
    being embedded into the code-mode `exec` tool declaration.
    
    Related to this:
    https://openai-corpws.slack.com/archives/C0AQLHB4U75/p1778660267922549
    
    ## What Changed
    
    - Adds `features.multi_agent_v2.non_code_mode_only`, including config
    resolution, profile override handling, and generated schema coverage.
    - Introduces `ToolExposure::DirectModelOnly` so a tool can be included
    in the initial model-visible list while staying out of the nested
    code-mode tool surface.
    - Applies that exposure to the multi-agent v2 tools when the new flag is
    set: `spawn_agent`, `send_message`, `followup_task`, `wait_agent`,
    `close_agent`, and `list_agents`.
    - Updates code-mode-only filtering so direct-model-only tools remain
    visible while ordinary nested code-mode tools are still hidden.
    
    ## Verification
    
    - Added config parsing/profile tests for `non_code_mode_only`.
    - Added tool spec coverage for the code-mode-only multi-agent v2
    exposure behavior.
  • [codex] Remove unused legacy shell tools (#22246)
    ## Why
    
    Recent session history showed no active use of the raw `shell`,
    `local_shell`, or `container.exec` execution surfaces. Keeping those
    handlers/specs wired into core leaves duplicate shell execution paths
    alongside the supported `shell_command` and unified exec tools.
    
    ## What changed
    
    - Removed the raw `shell` handler/spec and its `ShellToolCallParams`
    protocol helper.
    - Removed the legacy `local_shell` and `container.exec` handler/spec
    plumbing while preserving persisted-history compatibility for old
    response items.
    - Normalized model/config `default` and `local` shell selections to
    `shell_command`.
    - Pruned tests that exercised removed raw-shell/local-shell/apply-patch
    variants and kept coverage on `shell_command`, unified exec, and
    freeform `apply_patch`.
    
    ## Verification
    
    - `git diff --check`
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core tools::handlers::shell`
    - `cargo test -p codex-core tools::spec`
    - `cargo test -p codex-core tools::router`
    - `cargo test -p codex-core
    active_call_preserves_triggering_command_context`
    - `cargo test -p codex-core guardian_tests`
    - `cargo test -p codex-core --test all shell_serialization`
    - `cargo test -p codex-core --test all apply_patch_cli`
    - `cargo test -p codex-core --test all shell_command_`
    - `cargo test -p codex-core --test all local_shell`
    - `cargo test -p codex-core --test all otel::`
    - `cargo test -p codex-core --test all hooks::`
    - `just fix -p codex-core`
    - `just fix -p codex-tools`
  • Introduce tool exposure for deferred registration (#22489)
    ## Why
    
    Deferred tools were tracked with separate side-channel filtering after
    tool specs had already been assembled. That made the registry
    responsible for executing tools while the router/spec planner separately
    decided whether those same tools should be exposed to the model up
    front.
    
    This PR makes exposure part of the tool handler contract so direct
    versus deferred availability travels with the executable tool
    registration.
    
    Next step will be to simplify registration
    
    ## What Changed
    
    - Adds `ToolExposure` to `codex-tools` and exposes it through
    `ToolExecutor`, defaulting tools to `Direct`.
    - Teaches dynamic tools and MCP handlers to mark deferred tools as
    `Deferred` at construction time.
    - Renames the registry object-safe wrapper from `AnyToolHandler` to
    `RegisteredTool` and uses `ToolExposure` when deciding whether to
    include a handler's spec in the initial model-visible tool list.
    - Refactors tool spec planning to derive direct specs and deferred
    search entries from registered handlers, removing the router's
    special-case deferred dynamic tool filtering.
    
    ## Verification
    
    - Not run.
  • config: add strict config parsing (#20559)
    ## Why
    
    Codex intentionally ignores unknown `config.toml` fields by default so
    older and newer config files keep working across versions. That leniency
    also makes typo detection hard because misspelled or misplaced keys
    disappear silently.
    
    This change adds an opt-in strict config mode so users and tooling can
    fail fast on unrecognized config fields without changing the default
    permissive behavior.
    
    This feature is possible because `serde_ignored` exposes the exact
    signal Codex needs: it lets Codex run ordinary Serde deserialization
    while recording fields Serde would otherwise ignore. That avoids
    requiring `#[serde(deny_unknown_fields)]` across every config type and
    keeps strict validation opt-in around the existing config model.
    
    ## What Changed
    
    ### Added strict config validation
    
    - Added `serde_ignored`-based validation for `ConfigToml` in
    `codex-rs/config/src/strict_config.rs`.
    - Combined `serde_ignored` with `serde_path_to_error` so strict mode
    preserves typed config error paths while also collecting fields Serde
    would otherwise ignore.
    - Added strict-mode validation for unknown `[features]` keys, including
    keys that would otherwise be accepted by `FeaturesToml`'s flattened
    boolean map.
    - Kept typed config errors ahead of ignored-field reporting, so
    malformed known fields are reported before unknown-field diagnostics.
    - Added source-range diagnostics for top-level and nested unknown config
    fields, including non-file managed preference source names.
    
    ### Kept parsing single-pass per source
    
    - Reworked file and managed-config loading so strict validation reuses
    the already parsed `TomlValue` for that source.
    - For actual config files and managed config strings, the loader now
    reads once, parses once, and validates that same parsed value instead of
    deserializing multiple times.
    - Validated `-c` / `--config` override layers with the same
    base-directory context used for normal relative-path resolution, so
    unknown override keys are still reported when another override contains
    a relative path.
    
    ### Scoped `--strict-config` to config-heavy entry points
    
    - Added support for `--strict-config` on the main config-loading entry
    points where it is most useful:
      - `codex`
      - `codex resume`
      - `codex fork`
      - `codex exec`
      - `codex review`
      - `codex mcp-server`
      - `codex app-server` when running the server itself
      - the standalone `codex-app-server` binary
      - the standalone `codex-exec` binary
    - Commands outside that set now reject `--strict-config` early with
    targeted errors instead of accepting it everywhere through shared CLI
    plumbing.
    - `codex app-server` subcommands such as `proxy`, `daemon`, and
    `generate-*` are intentionally excluded from the first rollout.
    - When app-server strict mode sees invalid config, app-server exits with
    the config error instead of logging a warning and continuing with
    defaults.
    - Introduced a dedicated `ReviewCommand` wrapper in `codex-rs/cli`
    instead of extending shared `ReviewArgs`, so `--strict-config` stays on
    the outer config-loading command surface and does not become part of the
    reusable review payload used by `codex exec review`.
    
    ### Coverage
    
    - Added tests for top-level and nested unknown config fields, unknown
    `[features]` keys, typed-error precedence, source-location reporting,
    and non-file managed preference source names.
    - Added CLI coverage showing invalid `--enable`, invalid `--disable`,
    and unknown `-c` overrides still error when `--strict-config` is
    present, including compound-looking feature names such as
    `multi_agent_v2.subagent_usage_hint_text`.
    - Added integration coverage showing both `codex app-server
    --strict-config` and standalone `codex-app-server --strict-config` exit
    with an error for unknown config fields instead of starting with
    fallback defaults.
    - Added coverage showing unsupported command surfaces reject
    `--strict-config` with explicit errors.
    
    ## Example Usage
    
    Run Codex with strict config validation enabled:
    
    ```shell
    codex --strict-config
    ```
    
    Strict config mode is also available on the supported config-heavy
    subcommands:
    
    ```shell
    codex --strict-config exec "explain this repository"
    codex review --strict-config --uncommitted
    codex mcp-server --strict-config
    codex app-server --strict-config --listen off
    codex-app-server --strict-config --listen off
    ```
    
    For example, if `~/.codex/config.toml` contains a typo in a key name:
    
    ```toml
    model = "gpt-5"
    approval_polic = "on-request"
    ```
    
    then `codex --strict-config` reports the misspelled key instead of
    silently ignoring it. The path is shortened to `~` here for readability:
    
    ```text
    $ codex --strict-config
    Error loading config.toml:
    ~/.codex/config.toml:2:1: unknown configuration field `approval_polic`
      |
    2 | approval_polic = "on-request"
      | ^^^^^^^^^^^^^^
    ```
    
    Without `--strict-config`, Codex keeps the existing permissive behavior
    and ignores the unknown key.
    
    Strict config mode also validates ad-hoc `-c` / `--config` overrides:
    
    ```text
    $ codex --strict-config -c foo=bar
    Error: unknown configuration field `foo` in -c/--config override
    
    $ codex --strict-config -c features.foo=true
    Error: unknown configuration field `features.foo` in -c/--config override
    ```
    
    Invalid feature toggles are rejected too, including values that look
    like nested config paths:
    
    ```text
    $ codex --strict-config --enable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --disable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --enable multi_agent_v2.subagent_usage_hint_text
    Error: Unknown feature flag: multi_agent_v2.subagent_usage_hint_text
    ```
    
    Unsupported commands reject the flag explicitly:
    
    ```text
    $ codex --strict-config cloud list
    Error: `--strict-config` is not supported for `codex cloud`
    ```
    
    ## Verification
    
    The `codex-cli` `strict_config` tests cover invalid `--enable`, invalid
    `--disable`, the compound `multi_agent_v2.subagent_usage_hint_text`
    case, unknown `-c` overrides, app-server strict startup failure through
    `codex app-server`, and rejection for unsupported commands such as
    `codex cloud`, `codex mcp`, `codex remote-control`, and `codex
    app-server proxy`.
    
    The config and config-loader tests cover unknown top-level fields,
    unknown nested fields, unknown `[features]` keys, source-location
    reporting, non-file managed config sources, and `-c` validation for keys
    such as `features.foo`.
    
    The app-server test suite covers standalone `codex-app-server
    --strict-config` startup failure for an unknown config field.
    
    ## Documentation
    
    The Codex CLI docs on developers.openai.com/codex should mention
    `--strict-config` as an opt-in validation mode for supported
    config-heavy entry points once this ships.
  • [rollout-trace] Add a trace ID to MCP calls. (#22326)
    This allows us to connect individual tool calls to the logs of the
    invocations.
  • fix: main (#22503)
    Fix main due to conflicting merge
  • feat: add config-change extension contributor (#22488)
    ## Why
    
    Extensions can observe thread and turn lifecycle events today, but there
    was no single host-owned hook for changes to the effective thread
    configuration. That makes features that need to react to model,
    permission, or tool-suggest updates either depend on individual mutation
    paths or risk going stale after runtime config refreshes.
    
    This adds a typed config-change contributor so extension-owned state can
    stay synchronized with the effective thread config while the host
    remains responsible for deciding when config changed.
    
    ## What Changed
    
    - Added `ConfigContributor<C>` to `codex_extension_api`, with
    before/after immutable snapshots of the effective config plus
    session/thread extension stores.
    - Added registry builder/accessor support through `config_contributor`
    and `config_contributors`.
    - Emits config-change callbacks after committed updates from session
    settings, per-turn setting updates, and `refresh_runtime_config`.
    - Builds effective config snapshots only when config contributors are
    registered, and suppresses no-op callbacks when the before/after
    snapshots are equal.
    - Added a core session regression test that verifies contributors
    observe both model changes and user-layer runtime config changes,
    including access to session and thread extension stores.
    
    ## Validation
    
    Added `config_change_contributor_observes_effective_config_changes` in
    `codex-rs/core/src/session/tests.rs` to cover the new contributor path.
  • Add service tier overrides to spawned agents (#22139)
    ## Why
    
    Spawned agents can already override `model` and `reasoning_effort`, but
    they have no equivalent way to opt into a model-supported service tier.
    That makes it impossible to preserve or intentionally select tiered
    execution behavior when delegating work to a sub-agent, even though the
    model catalog already advertises supported `service_tiers`.
    
    ## What changed
    
    - Add optional `service_tier` to both legacy and `MultiAgentV2`
    `spawn_agent` tool inputs.
    - Show each picker-visible model's supported service tier ids and
    descriptions in the `spawn_agent` tool guidance.
    - Resolve service tier selection after the child agent's effective model
    is known.
    - Inherit the parent tier when omitted and still supported by the final
    child model; otherwise clear it.
    - Reject explicit unsupported tier requests with a model-facing error.
    - Keep explicit `service_tier` usable on full-history forks, while still
    honoring the existing model/reasoning fork restrictions.
    - Hide `service_tier` alongside other spawn metadata when
    `hide_spawn_agent_metadata` is enabled.
    
    ## Verification
    
    Added focused coverage for:
    
    - v1/v2 `spawn_agent` schema exposure for `service_tier`
    - tier descriptions in spawn guidance
    - hidden-metadata suppression
    - explicit supported tier selection
    - explicit unknown and unsupported tier rejection
    - inherited tier preservation or clearing based on child-model support
    - full-history fork acceptance for explicit service tiers in both v1 and
    v2
    
    Local Rust tests were not run in this workspace per repo guidance; the
    new coverage is included for CI.
  • feat(tui): remove Zellij TUI workarounds (#22214)
    ## Why
    
    We added Zellij-specific TUI workarounds because older Zellij behavior
    did not work with Codex's normal terminal model:
    
    - #8555 made `tui.alternate_screen = "auto"` disable alternate screen in
    Zellij so transcript history stayed available.
    - #16578 avoided scroll-region operations in Zellij by emitting raw
    newlines and using a separate composer styling path.
    
    This PR removes both workarounds because the latest Zellij release
    tested locally (`zellij 0.44.1`) works correctly with Codex's standard
    TUI behavior: normal alternate-screen handling, redraw, and history
    insertion.
    
    ## What Changed
    
    - Removed the `InsertHistoryMode::Zellij` path and the Zellij-only
    newline scrollback insertion behavior.
    - Removed cached `is_zellij` state from the TUI and composer.
    - Removed Zellij-specific composer styling, the helper snapshot, and the
    `TerminalInfo::is_zellij()` convenience method that only served this
    workaround.
    - Changed `tui.alternate_screen = "auto"` to use alternate screen for
    Zellij too; `--no-alt-screen` and `tui.alternate_screen = "never"` still
    preserve the inline mode escape hatch.
    - Updated the generated config schema description for
    `tui.alternate_screen`.
    
    ## How to Test
    
    Manual smoke path used with `zellij 0.44.1`:
    
    1. Build and run this branch inside a Zellij `0.44.1` session with
    default config.
    2. Start Codex normally and produce enough assistant/tool output to
    create scrollback.
    3. Confirm the transcript remains readable, the composer renders
    normally, and scrolling through terminal history works.
    4. Resize the Zellij pane while output exists and confirm the TUI
    redraws without duplicated, missing, or stale rows.
    5. Compare with `--no-alt-screen` or `-c tui.alternate_screen=never` if
    you want to verify the inline fallback still works.
    
    Targeted tests:
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `cargo test -p codex-terminal-detection`
    - `cargo test -p codex-tui alternate_screen_auto_uses_alt_screen`
    
    Attempted but did not complete locally:
    - `cargo test -p codex-tui` built and ran the new test successfully,
    then failed later on unrelated local failures in
    `status_permissions_full_disk_managed_*` and a stack overflow in
    `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`.
    
    ## Documentation
    
    No developers.openai.com Codex documentation update is needed for this
    revert.
  • Make context contributors async (#22491)
    ## Summary
    - make ContextContributor return a boxed Send future
    - await context contributors during initial context assembly
    - update existing contributors and extension-api examples for the async
    contract
    
    ## Testing
    - cargo test -p codex-extension-api --examples
    - cargo test -p codex-git-attribution
    - cargo test -p codex-core
    build_initial_context_includes_git_attribution_from_extensions --
    --nocapture
    - cargo test -p codex-core
    build_initial_context_omits_git_attribution_when_feature_is_disabled --
    --nocapture
    - cargo test -p codex-core (fails in unrelated
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
    stack overflow)
    - just fix -p codex-extension-api
    - just fix -p codex-git-attribution
    - just fix -p codex-core
    - cargo clippy -p codex-extension-api --examples
  • feat: move extension scope ids into ExtensionData (#22490)
    ## Summary
    - add a scoped level_id to ExtensionData and expose it through
    level_id()
    - remove thread_id/turn_id parameters from extension contributor inputs
    where the scoped ExtensionData already carries that identity
    - move turn-scoped extension data onto TurnContext so token usage and
    lifecycle contributors can share the same turn store
    
    ## Testing
    - cargo check -p codex-extension-api -p codex-core --tests
    - cargo test -p codex-extension-api
    - cargo test -p codex-guardian
    - cargo test -p codex-core --lib
    record_token_usage_info_notifies_extension_contributors
    - cargo test -p codex-core --lib
    submission_loop_channel_close_emits_thread_stop_lifecycle
    - cargo test -p codex-core --lib
    submission_loop_channel_close_aborts_active_turn_before_thread_stop_lifecycle
    - just fix -p codex-extension-api
    - just fix -p codex-guardian
    - just fix -p codex-core
    - just fmt
    
    ## Note
    - Attempted cargo test -p codex-core; it aborted in
    agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
    with the existing stack overflow before the full suite completed.
  • feat: add token usage contributor hook (#22485)
    ## Why
    
    Extensions need a stable place to observe token accounting after Codex
    folds model-provider usage into the session's cached `TokenUsageInfo`.
    Without a contributor hook, extension-owned features that need last-turn
    or cumulative token usage have to duplicate session plumbing or infer
    state from client-facing `TokenCount` notifications.
    
    ## What changed
    
    - Added `TokenUsageContributor` to `codex-extension-api`, passing
    session/thread `ExtensionData`, `ThreadId`, turn id, and the current
    `TokenUsageInfo`.
    - Added registry builder/storage support for token-usage contributors.
    - Invoked registered contributors from
    `Session::record_token_usage_info` after the session token cache is
    updated and before the client `TokenCount` notification is emitted.
    
    ## Testing
    
    - Added `record_token_usage_info_notifies_extension_contributors`,
    covering cumulative token usage updates and access to both extension
    stores.
  • fix: emit thread stop lifecycle on implicit shutdown (#22482)
    ## Why
    
    The thread lifecycle contributor hooks from #22476 should observe every
    session teardown. The explicit `Op::Shutdown` path already emitted
    `on_thread_stop`, but when `submission_loop` exited because its
    submission channel closed, it only tore down runtime services. That
    meant extensions could miss the thread-stop lifecycle signal on implicit
    runtime shutdown.
    
    ## What Changed
    
    - Split shared runtime teardown into `shutdown_runtime_services(...)`.
    - Split thread-stop lifecycle emission into
    `emit_thread_stop_lifecycle(...)`.
    - Reused those helpers from both explicit shutdown and the channel-close
    shutdown path.
    - Tracked whether `Op::Shutdown` was received so the explicit path does
    not double-emit lifecycle events after it exits the loop.
    - Added a regression test that closes the submission channel and asserts
    `ThreadLifecycleContributor::on_thread_stop` runs once with the expected
    thread/session stores.
    
    ## Testing
    
    - `cargo test -p codex-core
    submission_loop_channel_close_emits_thread_stop_lifecycle`
  • feat: add turn lifecycle contributors (#22480)
    ## Why
    
    Extensions can already contribute prompt, tool, turn-item, and
    thread-lifecycle behavior, but there was no explicit host-owned hook for
    per-turn setup and cleanup. That makes extension-private turn state
    awkward: an extension either has to stash it outside the turn lifecycle
    or depend on core runtime objects.
    
    This adds a small turn lifecycle boundary. Extensions receive stable
    identifiers plus the existing session, thread, and turn `ExtensionData`
    stores, while core keeps owning task scheduling, cancellation, and turn
    teardown.
    
    ## What Changed
    
    - Added `TurnLifecycleContributor` with `on_turn_start`, `on_turn_stop`,
    and `on_turn_abort` callbacks in `codex-rs/ext/extension-api`.
    - Added typed `TurnStartInput`, `TurnStopInput`, and `TurnAbortInput`
    payloads that expose `thread_id`, `turn_id`, `session_store`,
    `thread_store`, and `turn_store`.
    - Registered and re-exported turn lifecycle contributors through
    `ExtensionRegistry` and `ExtensionRegistryBuilder`.
    - Wired `Session` to emit turn start, stop, and abort callbacks from the
    existing turn/task lifecycle paths.
    - Carried the turn-scoped `ExtensionData` through `RunningTask` and
    `RemovedTask` so stop/abort callbacks receive the same turn store
    created at turn start.
    
    ## Verification
    
    - Not run locally.
  • 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`.
  • chore: Keep view_image sandbox test in temp dir (#22355)
    ## Summary
    - move the `view_image` sandbox filesystem-read unit test onto a
    temporary cwd
    - keep the turn cwd and selected turn environment cwd aligned inside the
    test
    - avoid leaving `core/image.png` behind in the repo checkout after the
    test runs
    
    ## Root cause
    The test wrote `image.png` beneath `turn.cwd`, and the shared session
    test helper defaults that cwd to the current repo directory when no
    override is provided.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-core
    tools::handlers::view_image::tests::handle_passes_sandbox_context_for_local_filesystem_reads`
  • add --dangerously-bypass-hook-trust CLI flag (#21768)
    # Why
    
    Hook trust happens through the TUI in `/hooks` so it can block
    non-interactive use cases. This flag will allow users that are using
    codex headlessly to bypass hooks when they want to.
    
    # What
    
    This adds one invocation-scoped escape hatch.
    
    - the CLI flag sets a runtime-only `bypass_hook_trust` override; there
    is no durable `config.toml` setting
    - hook discovery still respects normal enablement, so explicitly
    disabled hooks remain disabled
    - we show a `--dangerously-bypass-hook-trust is enabled. Enabled hooks
    may run without review for this invocation.` message on startup so
    accidental use is visible in both interactive and exec flows
    
    This keeps “enabled” and “trusted” as separate concepts in the normal
    path, while giving CI/E2E callers a stable way to opt into the
    exceptional path when they already control the hook set.
  • Use root repo hooks in linked worktrees (#21969)
    # Why
    
    Linked worktrees currently load their own project hook declarations, so
    the same repo can present different hook definitions depending on which
    checkout is active. https://github.com/openai/codex/pull/21762 tried to
    share trust by giving matching worktree hooks a shared synthetic key,
    but review pointed out that divergent worktree hook definitions would
    then fight over one `trusted_hash`.
    
    Instead of introducing a second trust model, this makes linked worktrees
    use the root checkout as the single source of truth for project hook
    declarations. Worktree-local project config can still diverge for
    unrelated settings, but project hooks now keep one real source path and
    one trust state per repo.
    
    # What
    
    - Teach project config loading to remember the matching root-checkout
    `.codex/` folder for actual linked-worktree project layers.
    - Keep ordinary project config sourced from the worktree, but replace
    project hook declarations with the root checkout's matching layer before
    hook discovery runs, including linked-worktree layers with `.codex/` but
    no local `config.toml`.
    - Make hook discovery use that authoritative hook folder for both
    `hooks.json` and TOML hook source paths, so linked worktrees produce the
    same hook key and trust state as the root checkout.
    - Cover the linked-worktree path plus regressions for missing worktree
    `config.toml` and nested non-worktree project roots.
  • Remove unavailable MCP placeholder tool backfill (#22439)
    ## Why
    
    `UnavailableDummyTools` kept synthetic placeholder tools alive for
    historical tool calls whose backing MCP tool was no longer available.
    That path adds stale model-visible tool specs and special routing at the
    point where unavailable MCP calls should use ordinary current-tool
    handling. This removes the runtime backfill instead of preserving a
    second compatibility lane.
    
    ## Is it safe to remove?
    
    The unavailable tools were added in #17853 after a CS issue when a
    previously-called MCP tool failed to load and was omitted from the CS
    spec. Now that we have tool search, I think this is resolved:
    - API merges tools from previous TST output into effective tool set so
    theyre always in CS spec
    - if an MCP tool surfaced by TST later becomes unavailable, the model
    can still call it and it will just return model-visible error
    - both TST output and function call output are dropped on compaction so
    model will not remember old calls to MCP post compaction
    
    ## What changed
    
    - Delete unavailable-tool collection, placeholder handler, router/spec
    plumbing, and obsolete placeholder coverage.
    - Keep `features.unavailable_dummy_tools` as a removed no-op feature
    tombstone so existing configs still parse cleanly.
    - Add an integration-style `tool_search` regression test showing that a
    deferred MCP tool surfaced through `tool_search` still routes through
    MCP and returns a model-visible tool-call error rather than `unsupported
    call`.
    
    ## Verification
    
    - `cargo test -p codex-core tool_search`
  • Encapsulate tool search entries in handlers (#22261)
    ## Why
    
    This builds on the handler-owned spec refactor by moving deferred
    tool-search metadata to the same handlers that already own tool specs.
    The registry builder no longer needs a separate prebuilt
    `tool_search_entries` path; it can collect searchable entries from
    deferred handlers directly.
    
    ## What changed
    
    - Added `search_info()` to tool handlers and implemented it for MCP and
    dynamic handlers.
    - Reused handler `spec()` output when constructing tool-search entries,
    adapting it into the deferred `LoadableToolSpec` shape expected by
    `tool_search`.
    - Simplified `build_tool_registry_builder(...)` so `tool_search`
    registration is based on deferred handlers with search info.
    - Removed the old standalone search-entry builders and now-unused
    `codex-tools` discovery helper exports.
    
    ## Verification
    
    - `cargo test -p codex-core tools::handlers::tool_search::tests:: --
    --nocapture`
    - `cargo test -p codex-core tools::spec_plan::tests::search_tool --
    --nocapture`
    - `cargo test -p codex-core tools::spec::tests:: -- --nocapture`
    - `cargo test -p codex-core tools::spec_plan::tests:: -- --nocapture`
    - `cargo test -p codex-tools`
    - `just fix -p codex-core`
    - `just fix -p codex-tools`
  • tools: infer code-mode namespace descriptions from specs (#22406)
    ## Why
    
    Code mode already builds the merged nested `ToolSpec`s that feed the
    `exec` prompt. Keeping a separate `tool_namespaces` map in the planning
    path duplicated that metadata and left extra wrapper plumbing in
    `spec.rs`.
    
    ## What changed
    
    - derive code-mode namespace descriptions from the merged
    `ToolSpec::Namespace` entries before building the code-mode handlers
    - extract `build_code_mode_handlers(...)` so the code-mode-specific
    planning stays in one place
    - remove `tool_namespaces` from `ToolRegistryBuildParams`
    - delete the now-unused `McpToolPlanInputs` wrapper and related test
    helper plumbing
    
    ## Testing
    
    - `cargo test -p codex-core spec_plan`
  • 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`
  • Add allow_managed_hooks_only hook requirement (#20319)
    ## Why
    
    Enterprise-managed hook policy needs a narrow way to require Codex to
    ignore user-controlled lifecycle hooks without adopting the broader
    trust-precedence model from earlier hook work. This keeps the policy
    anchored in `requirements.toml`, so admins can opt into managed hooks
    only while normal `config.toml` files cannot enable the restriction
    themselves.
    
    ## What changed
    
    - Added `allow_managed_hooks_only` to the requirements data flow and
    preserved explicit `false` values.
    - Also adds it to /debug-config
    - Marked MDM, system, and legacy managed config layers as managed for
    hook discovery.
    - Updated hook discovery so `allow_managed_hooks_only = true`:
      - keeps managed requirements hooks and managed config-layer hooks,
    - skips user/project/session `hooks.json` and `[hooks]` entries with
    concise startup warnings,
      - skips current unmanaged plugin hooks,
    - ignores any `allow_managed_hooks_only` key placed in ordinary
    `config.toml` layers.
  • hooks: use new session IDs instead of thread IDs for hooks, apply parent's session ID to subagents' hooks (#22268)
    ## Why
    
    hook semantics treat `session_id` as shared across a root session and
    its subagents. Codex hooks were still emitting the current thread ID,
    which made spawned agents look like independent sessions and made it
    harder for hook integrations to correlate work across a root thread and
    its spawned helpers
    
    This change makes hooks use Codex's existing shared session identity so
    hook `session_id` matches the root-thread session across spawned
    subagents.
    
    ## What Changed
    
    - switch hook payloads to use the existing shared session identity from
    core instead of the current thread ID
    - cover all hook surfaces that expose `session_id`, including
    `SessionStart`, tool hooks, compact hooks, prompt-submit hooks, stop
    hooks, and legacy after-agent dispatch
  • feat: route guardian review model selection through providers (#22258)
    ## Why
    
    Guardian review selection was hard-coded in `core`, which worked for the
    default OpenAI path but did not give provider implementations a way to
    choose backend-specific reviewer model IDs. That matters for Amazon
    Bedrock: guardian review should run through the Bedrock/Mantle provider
    using Bedrock's `openai.gpt-5.4` model ID, instead of accidentally
    selecting a reviewer model that implies the OpenAI backend.
    
    ## What Changed
    
    - Added provider-owned approval review model selection via
    `ModelProvider::approval_review_model_selection`.
    - Moved the existing default selection policy into the provider
    abstraction: prefer the requested reviewer model when it is available,
    otherwise fall back to the active turn model, preferring `Low` reasoning
    when supported.
    - Added an Amazon Bedrock override that pins guardian review to
    `openai.gpt-5.4` with `Low` reasoning.
  • [codex] Remove tool search bucket limit override (#22381)
    ## Why
    
    `tool_search` still carries the server-specific result-cap path added in
    #17684 for `computer-use`: when the model omitted `limit`, a matching
    result expanded the search to 20 and then `limit_results_by_bucket`
    applied per-bucket caps. That makes default result handling depend on a
    one-off server exception instead of the single
    `TOOL_SEARCH_DEFAULT_LIMIT` path.
    
    This PR removes that custom branch so omitted `limit` values use the
    ordinary global default consistently. The implementation being retired
    is the pre-change bucketed search path in
    [`tool_search.rs`](https://github.com/openai/codex/blob/5e3ee5eddfa5333f2e0b011880abf0cbf92bd295/codex-rs/core/src/tools/handlers/tool_search.rs#L121-L190).
    
    ## What changed
    
    - Collapse `ToolSearchHandler::search` back to one BM25 search with the
    resolved limit.
    - Remove `limit_results_by_bucket`, the `computer-use` constants, and
    the omitted-limit plumbing that only existed for the override.
    - Drop dead `ToolSearchEntry::limit_bucket` metadata from deferred MCP
    and dynamic search entries.
    - Remove tests and helpers that only asserted the deleted override
    behavior.
    - Add direct handler-level unit coverage for omitted/default and
    explicit `tool_search` result limits.
    
    ## Validation
    
    - `cargo test -p codex-core tool_search`
    - The matching unit tests passed, including the new omitted/default and
    explicit result-limit coverage.
    - The broader `--test all` search-tool fixture phase then failed before
    sending mocked response requests in
    `tool_search_indexes_only_enabled_non_app_mcp_tools` and
    `tool_search_uses_non_app_mcp_server_instructions_as_namespace_description`.
    - `cargo test -p codex-core`
    - The touched tool-search coverage passed before the run later aborted
    in
    `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`
    with a stack overflow.
  • 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
  • core: box multi-agent handler futures (#22266)
    ## Why
    
    This is the base PR in the split stack for the permissions migration. It
    isolates stack-safety work that had been mixed into the larger
    permissions PR, so reviewers can evaluate the async-future changes
    separately from the permissions model changes in #22267.
    
    The main risk this addresses is large or recursive multi-agent futures
    overflowing smaller runner stacks. A follow-up review also called out
    that `shutdown_live_agent` must remain quiescent: callers should not
    remove a live agent from tracking or release its spawn slot until the
    worker loop has actually terminated.
    
    ## What Changed
    
    - Boxes the large async futures in the multi-agent spawn, resume, and
    close tool handlers.
    - Boxes the `AgentControl` spawn and recursive close/shutdown paths that
    can otherwise build very deep futures.
    - Keeps `shutdown_live_agent` waiting for thread termination before
    removing/releasing the live agent, preserving the previous shutdown
    ordering while still boxing the recursive close path.
    
    ## Verification Strategy
    
    The focused local coverage was `cargo test -p codex-core multi_agents`,
    which exercises the multi-agent spawn/resume/close handlers, cascade
    close/resume behavior, and the shutdown path touched by this PR.
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22266).
    * #22330
    * #22329
    * #22328
    * #22327
    * __->__ #22266
  • Refactor namespaced tool spec registration (#22256)
    ## Summary
    
    This refactor makes tool handlers the owner of the specs they can
    publish, so registry construction can register handlers once and
    separately publish only the specs that should be model-visible.
    
    The main motivation is deferred tools: MCP and dynamic tools still need
    handlers registered up front, but deferred tools should be discoverable
    through `tool_search` rather than emitted in the initial tool spec list.
    
    ## What changed
    
    - `McpHandler` and `DynamicToolHandler` can return their own `ToolSpec`.
    - `build_tool_registry_builder` now collects handlers, registers them
    through the no-spec path, and publishes only non-deferred handler specs.
    - Deferred MCP and dynamic tool names are combined into one
    `all_deferred_tools` set that drives spec filtering, code-mode
    deferred-tool signaling, and `tool_search` registration.
    - `tool_search` registration now requires both deferred tools and
    `namespace_tools`.
    - Namespace specs are merged in `spec_plan`, preserving top-level spec
    order, sorting tools within each namespace, and backfilling empty
    namespace descriptions.
    - Hosted web search and image-generation specs are included in the
    collected spec vector before namespace merge/publication, and tool-name
    tests that should not care about hosted relative order now compare sets.
    
    ## Testing
    
    - `cargo test -p codex-core tools::spec::tests:: -- --nocapture`
    - `cargo test -p codex-core tools::spec_plan::tests:: -- --nocapture`
    - `cargo test -p codex-core
    tools::router::tests::specs_filter_deferred_dynamic_tools --
    --nocapture`
    - `cargo test -p codex-core
    suite::prompt_caching::prompt_tools_are_consistent_across_requests --
    --nocapture`
    - `just fmt`
    - `just fix -p codex-core`
    - `cargo test -p codex-core -- --skip
    tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`
    passed the library suite after skipping the known stack-overflowing unit
    test.
    
    Full `cargo test -p codex-core` currently hits a stack overflow in
    `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`;
    the same focused test reproduces on `origin/main`.
  • code-mode: carry nested tool kind through runtime (#22377)
    ## Why
    
    Code mode only used nested spec lookup at execution time to rediscover
    whether a nested tool should be invoked as a function tool or a freeform
    tool.
    
    That information is already present in the enabled tool metadata that
    code mode builds to expose `tools.*` and `ALL_TOOLS`, so re-looking it
    up from the router was redundant and kept execution coupled to a
    separate spec lookup path.
    
    ## What Changed
    
    - thread `CodeModeToolKind` through the code-mode runtime `ToolCall`
    event and `CodeModeNestedToolCall`
    - emit the nested tool kind directly from the V8 callback using the
    already-enabled tool metadata
    - build nested tool payloads from the propagated kind instead of calling
    `find_spec`
    - remove the now-unused `find_spec` plumbing from the router and
    parallel runtime helpers
    - add unit coverage for function vs freeform payload shaping and update
    affected router tests
    
    ## Testing
    
    - `cargo test -p codex-code-mode`
    - `cargo test -p codex-core code_mode::tests`
    - `cargo test -p codex-core
    extension_tool_bundles_are_model_visible_and_dispatchable`
    - `cargo test -p codex-core
    model_visible_specs_filter_deferred_dynamic_tools`
  • chore(config) include_collaboration_mode_instructions (#22383)
    ## Summary
    Adds include_collaboration_mode_instructions, which is a config
    equivalent to include_permissions_instructions for collaboration modes.
    Desired for situations where we want to disable this instruction from
    entering the context
    
    ## Testing
    - [x] Added unit test
  • 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`
  • [codex] Tighten unified exec sandbox setup (#22207)
    ## Summary
    - tighten unified exec sandbox initialization
    - preserve the requested process workdir independently from sandbox
    setup
    - add regression coverage for the updated invariant
    
    ## Validation
    - Ran `/tmp/cargo-tools/bin/just fmt`.
    - Ran the targeted `codex-core` regression test successfully.
    - Ran `cargo test -p codex-core`; it did not complete cleanly because
    unrelated existing agent/config-loader tests failed and the run later
    aborted on a stack overflow in
    `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`.
    
    Co-authored-by: Codex <noreply@openai.com>