Commit Graph

2689 Commits

  • stop blocking unified_exec on Windows (#19435)
    ## Summary
    - remove the Windows-specific unified-exec environment block from tool
    selection
    - keep `unified_exec` default-off on Windows unless the feature is
    explicitly enabled
    - normalize model-provided `shell_type = unified_exec` to
    `shell_command` when the feature is disabled
    - drop obsolete tests tied to the removed environment gate and keep the
    feature-flag regression coverage
    
    ## Why
    Now that the session/long-lived process backend is implemented for the
    Windows sandbox, we don't need to hard disable it anymore. We will be
    rolling out slowly using a feature gate.
    
    ## Impact
    This allows manual Windows opt-in in CLI and app-backed flows while
    preserving the existing default-off behavior for Windows users.
    
    ---------
    
    Co-authored-by: canvrno-oai <kbond@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • Add codex-core public API listing (#20243)
    Summary:
    - Add a checked-in codex-core public API listing generated by
    cargo-public-api.
    - Add scripts/regen-public-api.sh with an embedded crate list,
    auto-install for cargo-public-api 0.51.0, pinned nightly, and --check
    mode.
    - Add Rust CI jobs on the codex Linux x64 runner pool to verify the
    listing stays up to date.
    
    Testing:
    - bash -n scripts/regen-public-api.sh
    - just regen-public-api --check
    - yq '.' .github/workflows/rust-ci.yml
    .github/workflows/rust-ci-full.yml
    - git diff --check
  • [mcp] Fix plugin MCP approval policy. (#19537)
    Plugin MCP servers are loaded from plugin manifests rather than
    top-level `[mcp_servers]`, so their tool approval preferences need to be
    stored and applied through the owning plugin config. Without this,
    choosing "Always allow" for a plugin MCP tool could write a preference
    that was not reliably used on later tool calls.
    
    ## Summary
    - Add plugin-scoped MCP policy config under
    `plugins.<plugin>.mcp_servers`, including server enablement, tool
    allow/deny lists, server defaults, and per-tool approval modes.
    - Overlay plugin MCP policy onto manifest-provided server configs when
    plugins are loaded.
    - Route persistent "Always allow" writes for plugin MCP tools back to
    the owning `plugins.<plugin>.mcp_servers.<server>.tools.<tool>` config
    entry.
    - Reload user config after persisting an approval and make the plugin
    load cache config-aware so stale plugin MCP policy is not reused after
    `config.toml` changes.
    - Regenerate the config schema and add coverage for plugin MCP policy
    loading, approval lookup, persistence, and stale-cache prevention.
    
    ## Testing
    - `cargo test -p codex-config`
    - `cargo test -p codex-core-plugins`
    - `cargo test -p codex-core --lib plugin_mcp`
  • Escape turn metadata headers as ASCII JSON (#19620)
    ## Why
    
    `x-codex-turn-metadata` is sent as an HTTP/WebSocket header, but Codex
    was serializing the metadata JSON with raw UTF-8 string contents. When a
    workspace path contains non-ASCII characters, common HTTP stacks can
    reject or corrupt that header before the request reaches the provider.
    
    Fixes #17468. Also addresses the duplicate WebSocket report in #19581.
    
    ## What changed
    
    - Added `codex_utils_string::to_ascii_json_string`, a shared helper that
    serializes JSON normally while escaping non-ASCII string content as
    `\uXXXX`.
    - Switched turn metadata header serialization, including merged
    Responses API client metadata, to use the ASCII-safe JSON helper.
    - Added coverage for non-ASCII workspace paths and non-ASCII client
    metadata while preserving the same parsed JSON values.
    
    ## Verification
    
    - `cargo test -p codex-utils-string`
    - `cargo test -p codex-core turn_metadata`
    - `just bazel-lock-check`
  • [apps] Add apps MCP path override (#20231)
    Summary
    
    - Add `[features.apps_mcp_path_override]` config with a `path` field for
    overriding only the built-in apps MCP path.
    - Keep existing host/base URL derivation unchanged and append the
    configured path after that base.
    - Regenerate the config schema with the custom feature-config case.
    
    Test Plan
    
    - Not run for latest revision; only `just fmt` and `just
    write-config-schema` were run.
    - Earlier revision: `cargo test -p codex-features`
    - Earlier revision: `cargo test -p codex-mcp`
  • fix: handle deferred network proxy denials (#19184)
    ## Why
    
    This bug is exposed by Guardian/auto-review approvals. With the managed
    network proxy enabled, a blocked network request can be reported back
    through the network approval service as an approval denial after the
    command has already started. Before this change, the shell and unified
    exec runtimes registered those network approval calls, but did not have
    a way to observe an async proxy denial as a cancellation/failure signal
    for the running process.
    
    The result was confusing: Guardian/auto-review could correctly deny
    network access, but the command path could keep running or unregister
    the approval without surfacing the denial as the command failure.
    
    ## What Changed
    
    - `NetworkApprovalService` now attaches a cancellation token to active
    and deferred network approvals.
    - Proxy-denial outcomes are recorded only for active registrations,
    cancel the owning token, and are consumed when the approval is
    finalized.
    - The shell runtime combines the normal command timeout with the
    network-denial cancellation token.
    - Unified exec stores the deferred network approval object, terminates
    tracked processes when the proxy denial arrives, and returns the denial
    as a process failure while polling or completing the process.
    - Tool orchestration passes the active network approval cancellation
    token into the sandbox attempt and preserves deferred approval errors
    instead of silently unregistering them.
    - App-server `command/exec` now handles the combined
    timeout-or-cancellation expiration variant used by the runtime.
    
    ## Verification
    
    - `cargo test -p codex-core network_approval --lib`
    - `cargo clippy -p codex-app-server --all-targets -- -D warnings`
    - `cargo clippy -p codex-core --all-targets -- -D warnings`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: Use remote installed plugin cache for skills and MCP (#20096)
    - Fetches and caches remote /installed plugin state
    - Lets skills/list load skills from remote-installed cached plugins
    without requiring a local marketplace entry
    - Routes plugin list/startup/install/uninstall changes through async
    plugin cache invalidation and MCP refresh
  • Include auto-review rollout in feedback uploads (#20064)
    ## Summary
    
    - include the live auto-review trunk rollout when `/feedback` uploads
    logs
    - upload that attachment as
    `auto-review-rollout-<parent-thread-id>.jsonl` so it is distinguishable
    from the parent rollout
    - show the same auto-review attachment name in the TUI consent popup
    
    ## Scope
    
    - this only covers the live cached auto-review trunk for the current
    parent thread
    - it does not add durable historical parent->auto-review lookup
    - it does not add persisted rollout support for ephemeral parallel
    review forks
    
    ## UI 
    
    <img width="599" height="185" alt="Screenshot 2026-04-28 at 1 17 18 PM"
    src="https://github.com/user-attachments/assets/6a0e79c2-5d21-4702-8a89-f765778bc9e9"
    />
    
    ## Validation
    
    - `cargo test -p codex-core
    cached_guardian_subagent_exposes_its_rollout_path`
    - `cargo test -p codex-feedback`
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-tui feedback_upload_consent_popup_snapshot`
    - `cargo test -p codex-tui
    feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename`
    
    ## Known unrelated local failures
    
    - `cargo test -p codex-core` currently fails in the pre-existing proxy
    env snapshot test
    `tools::runtimes::tests::maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive`
    - `cargo test -p codex-tui` currently hits pre-existing `status::*`
    snapshot drift unrelated to this change
    
    ## Follow-Up 
    - persist parallel auto-review fork sessions so /feedback can include
    their rollout history too
    - attach each persisted fork as its own clearly named file, for example
    auto-review-rollout-<parent-thread-id>-fork <n>.jsonl, instead of
    merging multiple Guardian sessions into one attachment
    - keep the same live-session-only scope initially; durable historical
    parent -> auto-review lookup can remain a separate decision if we later
    need feedback from resumed sessions
  • Add ThreadManager sample crate (#20141)
    Summary:
    - Add codex-thread-manager-sample, a one-shot binary that starts a
    ThreadManager thread, submits a prompt, and prints the final assistant
    output.
    - Pass ThreadStore into ThreadManager::new and expose
    thread_store_from_config for existing callsites.
    - Build the sample Config directly with only --model and prompt inputs.
    
    Verification:
    - just fmt
    - cargo check -p codex-thread-manager-sample -p codex-app-server -p
    codex-mcp-server
    - git diff --check
    
    Tests: Not run per request.
  • Make multi-agent v2 ignore agents.max_depth (#20180)
    ## Why
    
    `agents.max_depth` is a legacy multi-agent v1 guard. Multi-agent v2 uses
    task-path routing and its own session/thread limits, so v2 should not
    reject nested `spawn_agent` calls just because the thread-spawn depth
    has reached the v1 maximum.
    
    Keeping the v1 depth guard active in v2 prevents deeper task trees even
    though the v2 path still needs the depth value only for lineage and
    task-path metadata.
    
    ## What Changed
    
    - Removed the depth-limit rejection from the multi-agent v2
    `spawn_agent` handler while still computing child depth for lineage/path
    metadata.
    - Made the depth-based disabling of legacy `SpawnCsv`/`Collab` tools
    apply only when `Feature::MultiAgentV2` is disabled.
    - Added `multi_agent_v2_spawn_agent_ignores_configured_max_depth` to
    cover a v2 child spawning another agent when `agent_max_depth = 1`,
    while the existing v1 depth-limit tests continue to enforce the legacy
    behavior.
    
    ## Verification
    
    - `cargo test -p codex-core
    multi_agent_v2_spawn_agent_ignores_configured_max_depth -- --nocapture`
    - `cargo test -p codex-core depth_limit -- --nocapture`
    - `cargo test -p codex-core tools::handlers::multi_agents::tests --
    --nocapture`
  • feat(cli): add sandbox profile config controls (#20118)
    ## Why
    
    The explicit profile path from #20117 is meant for standalone testing,
    but it still inherited the
    shell cwd and all managed requirements implicitly. The pre-existing
    launcher path even called out
    that it did not support a separate cwd yet in
    
    [`debug_sandbox.rs`](https://github.com/openai/codex/blob/509453f688a30929432be866402d1ea46aa12169/codex-rs/cli/src/debug_sandbox.rs#L174-L179).
    
    For a standalone command, the useful default is to let the caller choose
    the project directory being
    tested and to avoid administrator-provided constraints unless the caller
    explicitly wants to test
    those too.
    
    ## What changed
    
    - Add explicit-profile-only `-C/--cd DIR`, and use that cwd for both
    profile resolution and command
      execution.
    - Add explicit-profile-only `--include-managed-config`.
    - Make explicit profile mode skip managed requirement sources by
    default, including cloud
    requirements, MDM requirements, `/etc/codex/requirements.toml`, and the
    legacy managed-config
      requirements projection.
    - Preserve all existing invocations outside the explicit-profile path.
    
    ## Stack
    
    1. #20117 `sandbox-ui-profile`
    2. #20118 `sandbox-ui-config` --> this PR
    
    Both PRs are additive. Replay JSON is intentionally deferred to a
    follow-up design pass.
    
    ## Tests ran
    
    - `cargo test -p codex-cli debug_sandbox`
    - `cargo test -p codex-cli sandbox_macos_`
    - `cargo test -p codex-core
    load_config_layers_can_ignore_managed_requirements`
    - `cargo test -p codex-core
    load_config_layers_includes_cloud_requirements`
    - macOS branch-binary smoke on the rebased top of stack: `-C` changed
    execution cwd, explicit
    profile mode omitted managed proxy env under `env -i`, and
    `--include-managed-config` restored it.
    - Linux devbox branch-binary smoke on the rebased top of stack: `-C`
    changed execution cwd for
      built-in and user-defined explicit profiles.
  • Delete multi_agent_v2 followup_task interrupt parameter (#20139)
    Messages sent with `followup_task` already arrive at their target
    recipient promptly (at message boundaries while sampling, or after the
    pending tool call completes) -- having `interrupt` is not worth the
    added complexity.
  • feat(cli): add explicit sandbox permission profiles (#20117)
    ## Why
    
    `codex sandbox` is useful for exercising sandbox behavior directly, but
    before this stack the CLI
    only picked up permission profiles indirectly from the active config.
    The existing debug-sandbox path
    already compiled `[permissions]` profiles through normal config loading,
    as covered by the existing
    profile tests in
    [`debug_sandbox.rs`](https://github.com/openai/codex/blob/de2ccf94735a3d8a2a7077e6a5292026413867cf/codex-rs/cli/src/debug_sandbox.rs#L715-L760).
    
    This adds the smallest stable entry point first: an explicit profile
    selector that reuses the same
    config machinery as normal Codex config, so standalone testing becomes
    possible without changing
    current no-selector behavior.
    
    ## What changed
    
    - Add additive `--permissions-profile NAME` support to `codex sandbox
    macos|linux|windows`.
    - Resolve built-in and user-defined profile names by feeding
    `default_permissions` through the
    existing config compilation path instead of inventing a sandbox-only
    parser.
    - Make an explicit selector win over an ambient active profile's legacy
    `sandbox_mode`.
    - Keep the existing no-selector behavior unchanged.
    
    ## Stack
    
    1. #20117 `sandbox-ui-profile` --> this PR
    2. #20118 `sandbox-ui-config`
    
    Both PRs are additive. Replay JSON is intentionally deferred to a
    follow-up design pass.
    
    ## Tests ran
    
    - `cargo test -p codex-cli debug_sandbox`
    - `cargo test -p codex-cli sandbox_macos_parses_permissions_profile`
    - `cargo test -p codex-core
    cli_override_takes_precedence_over_profile_sandbox_mode`
    - macOS branch-binary smoke on the rebased top of stack: built-in
    `:workspace` and user-defined
      profiles both executed successfully through `--permissions-profile`.
    - Linux devbox branch-binary smoke on the rebased top of stack: built-in
    `:workspace` and
    user-defined profiles both executed successfully through
    `--permissions-profile`.
  • Add environment provider snapshot (#20058)
    ## Summary
    - Change `EnvironmentProvider` to return concrete `Environment`
    instances instead of `EnvironmentConfigurations`.
    - Make `DefaultEnvironmentProvider` provide the provider-visible `local`
    environment plus optional `remote` environment from
    `CODEX_EXEC_SERVER_URL`.
    - Keep `EnvironmentManager` as the concrete cache while exposing its own
    explicit local environment for `local_environment()` fallback paths.
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Soften skill description budget warnings (#20112)
    Updates skill description budget messaging to be less alarming
  • linux-sandbox: switch helper plumbing to PermissionProfile (#20106)
    ## Why
    
    `PermissionProfile` is the canonical runtime permission model in the
    Rust workspace, but the Linux sandbox helper still accepted a legacy
    `SandboxPolicy` plus separate filesystem and network policy flags. That
    translation layer made the helper interface harder to reason about and
    left `linux-sandbox`-specific callers and tests coupled to the legacy
    policy representation.
    
    This change moves the helper onto `PermissionProfile` directly so the
    Linux sandbox plumbing matches the rest of the permission stack.
    
    ## What changed
    
    - changed `codex-linux-sandbox` to accept `--permission-profile` and
    derive the runtime filesystem and network policies internally
    - updated the in-process seccomp and legacy Landlock path in
    `codex-rs/linux-sandbox` to operate on `PermissionProfile`
    - updated Linux sandbox argv construction in `codex-rs/sandboxing`,
    `codex-rs/core`, and the CLI debug sandbox path to pass the canonical
    profile instead of serializing compatibility policy projections
    - simplified the Linux sandbox tests to build the exact permission
    profile under test, including the managed-proxy path and
    direct-runtime-enforcement carveout coverage
    - removed helper-local `SandboxPolicy` usage from `bwrap` tests where
    `FileSystemSandboxPolicy` is already the value being exercised
    
    ## Testing
    
    - `cargo test -p codex-sandboxing`
    - `cargo test -p codex-linux-sandbox` (on this macOS host, the crate
    compiled cleanly and its Linux-only tests were cfg-gated)
    - `cargo test -p codex-core --no-run`
    - `cargo test -p codex-cli --no-run`
  • feat: disable capabilities by model provider (#19442)
    ## Why
    
    Unsupported features must fail closed and Codex must not expose
    OpenAI-hosted fallback paths when the active provider cannot support
    them. In practice, Bedrock should not surface app connectors, MCP
    servers, tool search/suggestions, image generation, web search, or JS
    REPL until those paths are explicitly supported for that provider.
    
    This PR moves that decision into provider-owned capability metadata
    instead of scattering Bedrock-specific checks across callers.
    
    ## What changed
    
    - Adds `ProviderCapabilities` to `codex-model-provider`, with default
    support for existing providers and a Bedrock override that disables
    unsupported launch surfaces.
    - Adds `ToolCapabilityBounds` to `codex-tools` so provider capability
    limits can clamp otherwise-enabled tool config.
    - Applies capability bounds when building session and review-thread tool
    config.
    - Routes MCP/app connector configuration through
    `McpManager::mcp_config`, which filters configured MCP servers and app
    connectors based on the active provider.
    - Updates app-server MCP list/read paths to use the filtered MCP config.
    - Adds coverage for default provider capabilities, Bedrock disabled
    capabilities, and optional tool-surface clamping.
    
    ## Testing
    
    built locally and verified that bedrock responses api now return without
    errors calling unsupported tools.
  • Support disabling tool suggest for specific tools. (#20072)
    ## Summary
    - Add `disable_tool_suggest` to app and plugin config, schema, and
    TypeScript output
    - Exclude disabled connectors and plugins from tool suggestion discovery
    - Persist "never show again" tool-suggestion choices back into
    `config.toml`
    - Update config docs and add coverage for connector and plugin
    suppression
    
    ## Testing
    - Added and updated unit tests for config persistence and tool-suggest
    filtering
    - Not run (not requested)
  • Discover hooks bundled with plugins (#19705)
    ## Why
    
    Plugins can bundle lifecycle hooks, but Codex previously only discovered
    hooks from user, project, and managed config layers. This adds the
    plugin discovery and runtime plumbing needed for plugin-bundled hooks
    while keeping execution behind the `plugin_hooks` feature flag.
    
    ## What
    
    - Discovers plugin hook sources from each plugin's default
    `hooks/hooks.json`.
    - Supports `plugin.json` manifest `hooks` entries as either relative
    paths or inline hook objects.
    - Plumbs discovered plugin hook sources through plugin loading into the
    hook runtime when `plugin_hooks` is enabled.
    - Marks plugin-originated hook runs as `HookSource::Plugin`.
    - Injects `PLUGIN_ROOT` and `CLAUDE_PLUGIN_ROOT` into plugin hook
    command environments.
    - Updates generated schemas and hook source metadata for the plugin hook
    source.
    
    ## Stack
    
    1. This PR - openai/codex#19705
    2. openai/codex#19778
    3. openai/codex#19840
    4. openai/codex#19882
    
    ## Reviewer Notes
    
    - Core logic is in `codex-rs/core-plugins/src/loader.rs` and
    `codex-rs/hooks/src/engine/discovery.rs`
    - Moved existing / adding new tests to
    `codex-rs/core-plugins/src/loader_tests.rs` hence the large diff there
    - Otherwise mostly plumbing and minor schema updates
    
    ### Core Changes
    
    The `codex-rs/core` changes are limited to wiring plugin hook support
    into existing core flows:
    
    - `core/src/session/session.rs` conditionally pulls effective plugin
    hook sources and plugin hook load warnings from `PluginsManager` when
    `plugin_hooks` is enabled, then passes them into `HooksConfig`.
    - `core/src/hook_runtime.rs` adds the `plugin` metric tag for
    `HookSource::Plugin`.
    - `core/config.schema.json` picks up the new `plugin_hooks` feature
    flag, and `core/src/plugins/manager_tests.rs` updates fixtures for the
    added plugin hook fields.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [rollout-trace] Include x-request-id in rollout trace. (#20066)
    ## Why
    
    Rollout traces need an identifier that can be used to correlate a Codex
    inference with upstream Responses API, proxy, and engine logs. The
    reduced trace model already exposed `upstream_request_id`, but it was
    being populated from the Responses API `response.id`. That value is
    useful for `previous_response_id` chaining, but it is not the transport
    request id that upstream systems key on.
    
    This PR separates those concepts so trace consumers can reliably answer
    both questions:
    
    - which Responses API response did this inference produce?
    - which upstream request handled it?
    
    ## Structure
    
    The change keeps the upstream request id at the same lifecycle level as
    the provider stream:
    
    - `codex-api` captures the `x-request-id` HTTP response header when the
    SSE stream is created and exposes it on `ResponseStream`. Fixture and
    websocket streams set the field to `None` because they do not have that
    HTTP response header.
    - `codex-core` carries that stream-level id into `InferenceTraceAttempt`
    when recording terminal stream outcomes. Completed, failed, cancelled,
    dropped-stream, and pre-response error paths all record the id when it
    is available.
    - `rollout-trace` now records both identifiers in raw terminal inference
    events and response payloads: `response_id` for the Responses API
    `response.id`, and `upstream_request_id` for `x-request-id`.
    - The reducer stores both fields on `InferenceCall`. It also uses
    `response_id` for `previous_response_id` conversation linking, which
    removes the old accidental dependency on the misnamed
    `upstream_request_id` field.
    - Terminal inference reduction now consumes the full terminal payload
    (`InferenceCompleted`, `InferenceFailed`, or `InferenceCancelled`) in
    one place. That keeps status, partial payloads, response ids, and
    upstream request ids consistent across success, failure, cancellation,
    and late stream-mapper events.
    
    ## Why This Shape
    
    `x-request-id` is a property of the HTTP/provider response envelope, not
    an SSE event. Capturing it once in `codex-api` and plumbing it through
    terminal trace recording avoids trying to infer the value from stream
    contents, and it preserves the id even when the stream fails or is
    cancelled after only partial output.
    
    Keeping `response_id` separate from `upstream_request_id` also makes the
    reduced trace model less surprising: `response_id` remains the
    conversation-continuation id, while `upstream_request_id` is the
    operational correlation id for upstream debugging.
    
    ## Validation
    
    The PR updates trace and reducer coverage for:
    
    - reading `x-request-id` from SSE response headers;
    - storing the true upstream request id on completed inference calls;
    - preserving upstream request ids for cancelled and late-cancelled
    inference streams;
    - keeping `previous_response_id` reconstruction tied to `response_id`
    rather than transport request ids.
  • Make MultiAgentV2 wait minimum configurable (#20052)
    ## Why
    
    MultiAgentV2 `wait_agent` currently clamps short waits to a fixed 10
    second minimum. That default is still useful for preventing tight
    polling loops, but it is too rigid for environments that need faster
    mailbox wake-up checks or a larger minimum to discourage frequent
    polling.
    
    This PR makes the minimum wait timeout configurable from the existing
    MultiAgentV2 feature config section, so operators can tune the behavior
    without changing the legacy multi-agent tool surface.
    
    ## What Changed
    
    - Added `features.multi_agent_v2.min_wait_timeout_ms`.
    - Defaulted the new setting to the existing 10 second floor.
    - Validated the configured value as `1..=3600000`, matching the existing
    one hour maximum wait bound.
    - Applied the configured minimum to MultiAgentV2 `wait_agent` runtime
    clamping.
    - Plumbed the configured minimum into the `wait_agent` tool schema,
    including the effective default when the minimum is above the normal 30
    second default.
    - Regenerated `core/config.schema.json`.
    
    ## Verification
    
    - `cargo test -p codex-features`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core --lib multi_agent_v2`
    - `just fix -p codex-core`
  • [codex] Add token usage to turn tracing spans (#19432)
    ## Why
    
    Slow Codex turns are easier to debug when token usage is visible in the
    trace itself, without joining against separate analytics. This adds
    token usage to existing turn-handling spans for regular user turns only.
    
    [Example
    turn](https://openai.datadoghq.com/apm/trace/9d353efa2cb5de1f4c5b93dc33c3df04?colorBy=service&graphType=flamegraph&shouldShowLegend=true&sort=time&spanID=3555541504891512675&spanViewType=metadata&traceQuery=)
    <img width="1447" height="967" alt="Screenshot 2026-04-24 at 3 03 07 PM"
    src="https://github.com/user-attachments/assets/ab7bb187-e7fc-41f0-a366-6c44610b2b2c"
    />
    
    ## What Changed
    
    Added response-level token fields on completed handle_responses spans:
    
    gen_ai.usage.input_tokens
    gen_ai.usage.cache_read.input_tokens
    gen_ai.usage.output_tokens
    codex.usage.reasoning_output_tokens
    codex.usage.total_tokens
    Added aggregate token fields on regular turn spans:
    
    codex.turn.token_usage.*
    Added an explicit regular-turn opt-in via
    SessionTask::records_turn_token_usage_on_span() so this is not coupled
    to span-name strings.
    
    ## Testing
    
    - `cargo test -p codex-otel`
    - `cargo test -p codex-core
    turn_and_completed_response_spans_record_token_usage`
    - `just fmt`
    - `just fix -p codex-core`
    - `just fix -p codex-otel`
    - Manual local Electron/app-server smoke test: regular user turn emits
    the new span fields
    
    Known status: `cargo test -p codex-core` was attempted and failed in
    unrelated existing areas: config approvals, request-permissions,
    git-info ordering, and subagent metadata persistence.
  • permissions: add built-in default profiles (#19900)
    ## Why
    
    The migration away from `SandboxPolicy` needs new configs to start from
    permissions profiles instead of deriving profiles from legacy sandbox
    modes. Existing users can have empty `config.toml` files, and we should
    not rewrite user-owned config files that may live in shared
    repositories.
    
    This PR introduces built-in profile names so an empty config can resolve
    to a canonical `PermissionProfile`, while explicit named `[permissions]`
    profiles still behave predictably.
    
    ## What changed
    
    - Adds built-in `default_permissions` profile names:
      - `:read-only` maps to `PermissionProfile::read_only()`.
    - `:workspace` maps to the workspace-write profile, including
    project-root metadata carveouts.
    - `:danger-no-sandbox` maps to `PermissionProfile::Disabled`, preserving
    the distinction between no sandbox and a broad managed sandbox.
    - Reserves the `:` prefix for built-in profiles so user-defined
    `[permissions]` profiles cannot collide with future built-ins.
    - Allows `default_permissions` to reference a built-in profile without
    requiring a `[permissions]` table.
    - Makes an otherwise empty config choose a built-in profile by
    trust/platform context: trusted or untrusted project roots use
    `:workspace` when the platform supports that sandbox, while roots
    without a trust decision use `:read-only`.
    - Keeps legacy `sandbox_mode` configs on the legacy path, and still
    rejects user-defined `[permissions]` profiles that omit
    `default_permissions` so we do not silently guess among custom profiles.
    - Preserves compatibility behavior for implicit defaults: bare
    `network.enabled = true` allows runtime network without starting the
    managed proxy, explicit profile proxy policy still starts the proxy, and
    implicit workspace/add-dir roots keep legacy metadata carveouts.
    
    ## Verification
    
    - `cargo test -p codex-core builtin --lib`
    - `cargo test -p codex-core profile_network_proxy_config`
    - `cargo test -p codex-core
    implicit_builtin_workspace_profile_preserves_add_dir_metadata_carveouts`
    - `cargo test -p codex-core
    permissions_profiles_network_enabled_allows_runtime_network_without_proxy`
    - `cargo test -p codex-core
    permissions_profiles_proxy_policy_starts_managed_network_proxy`
    
    ## Documentation
    
    Public Codex config docs should mention these built-in names when the
    `[permissions]` config format is ready to document as stable.
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19900).
    * #20041
    * #20040
    * #20037
    * #20035
    * #20034
    * #20033
    * #20032
    * #20030
    * #20028
    * #20027
    * #20026
    * #20024
    * #20021
    * #20018
    * #20016
    * #20015
    * #20013
    * #20011
    * #20010
    * #20008
    * __->__ #19900
  • Clarify network approval auto-review prompts (#19907)
    ## Why
    
    Network access approval prompts were showing the generic retry reason,
    which made auto-review focus on the blocked connection instead of the
    command that caused it. This makes network approvals easier to assess by
    telling the reviewer to evaluate whether the triggering command was
    authorised by the user and within policy, and to treat the network call
    as acceptable when it is a reasonable consequence of that command.
    
    ## What changed
    
    - Split guardian approval request prompt rendering so `NetworkAccess`
    has a dedicated branch.
    - For network requests, show `Network approval context` and `Network
    access JSON` instead of `Retry reason` / `Planned action JSON`.
    - Added regression coverage for the network approval prompt wording and
    for omitting retry reason in this case.
    
    ## Verification
    
    - `cargo test -p codex-core
    guardian::tests::build_guardian_prompt_items_explains_network_access_review_scope`
  • Record MCP result telemetry on mcp.tools.call spans (#19509)
    ## Why
    - Without change: MCP tool call spans include request-side details such
    as server, tool, call ID, connector, session, and turn.
    - Issue: Some useful telemetry is only known by the MCP server after it
    handles the tool call, such as target identity or whether the call
    triggered a user-facing flow.
    
    ## What Changed
    - With change: Codex reads allowlisted telemetry from
    `_meta["codex/telemetry"]["span"]` and records it on the
    `mcp.tools.call` span.
    - Adds span fields for `codex.mcp.target.id` and
    `codex.mcp.user_flow.triggered`, with strict type checks and bounded
    target ID length.
    
    
    ## Verification
    `codex-rs/core/src/mcp_tool_call_tests.rs`
  • Add turn start timestamp to turn metadata (#19473)
    ## Why
    - Without change: MCP tool calls receive
    `_meta["x-codex-turn-metadata"]` with `session_id` and `turn_id`.
    - Issue: MCP servers may want the turn start timestamp to measure
    internal latency relative to turn start.
    
    ## What Changed
    - With change: turn metadata now includes `turn_started_at_unix_ms`,
    which is propagated to MCP tool calls in
    `_meta["x-codex-turn-metadata"]`.
    
    ## Verification
    - `codex-rs/core/src/mcp_tool_call_tests.rs`
    - `codex-rs/core/src/turn_metadata_tests.rs`
    - `codex-rs/core/src/turn_timing_tests.rs`
    - `codex-rs/core/tests/responses_headers.rs`
    - `codex-rs/core/tests/suite/search_tool.rs`
  • Terminate stdio MCP servers on shutdown to avoid process leaks (#19753)
    ## Why
    
    Several bug reports describe thread shutdown (including subagent
    threads) leaving stdio MCP server processes behind. These reports all
    point at the same lifecycle gap: Codex launches stdio MCP servers, but
    the session-level shutdown path does not explicitly close MCP clients or
    terminate the server process tree.
    
    Fixes #12491
    Fixes #12976
    Fixes #18881
    Fixes #19469
    
    ## History
    
    This is best understood as a regression/coverage gap in MCP session
    lifecycle management, not as stdio MCP cleanup being absent all along.
    #10710 added process-group cleanup for stdio MCP servers, but that
    cleanup only runs when the `RmcpClient`/transport is dropped. The older
    reports (#12491 and #12976) came after that cleanup existed, which
    suggests the remaining problem was that some higher-level shutdown paths
    kept the MCP manager alive or replaced it without explicitly draining
    clients. The newer reports (#18881 and #19469) exposed the same family
    around manager replacement and shutdown.
    
    ## What changed
    
    - Added an explicit stdio MCP process handle in `codex-rmcp-client` so
    local MCP servers terminate their process group and executor-backed MCP
    servers call the executor process terminator.
    - Added `RmcpClient::shutdown()` and manager-level MCP shutdown draining
    so session shutdown, channel-close fallback, MCP refresh, and connector
    probing stop owned MCP clients.
    - Added regression coverage that starts a stdio MCP server, begins an
    in-flight blocking tool call, shuts down the client, and asserts the
    server process exits.
    
    ## Verification
    
    - `cargo test -p codex-rmcp-client`
    - `cargo test -p codex-mcp`
    - `just fix -p codex-rmcp-client`
    - `just fix -p codex-mcp`
    - `just fix -p codex-core`
    
    - Manual before/after validation with a temporary repro script:
    - Pre-fix binary from `HEAD^` (`fed0a8f4fa`): reproduced the leak with
    surviving MCP server and child PIDs, `survivors=[77583, 77592]`,
    `leaked=true`.
    - Post-fix binary from this branch (`67e318148b`): verified both MCP
    processes were gone after interrupting `codex exec`, `survivors=[]`,
    `leaked=false`.
  • feat(tui): add configurable keymap support (#18593)
    ## Why
    
    The TUI currently handles keyboard shortcuts as hard-coded event matches
    spread across app, composer, pager, list, approval, and navigation code.
    That makes shortcuts hard to customize, makes displayed hints easy to
    drift from actual behavior, and makes future keymap work riskier because
    there is no central action inventory.
    
    This PR adds the foundation for configurable, action-based keymaps
    without adding the interactive remapping UI yet. Onboarding
    intentionally stays on fixed startup shortcuts because users cannot
    reasonably configure keymaps before completing onboarding.
    
    This is PR1 in the keymap stack:
    
    - PR1: #18593: configurable keymap foundation
    - PR2: #18594: `/keymap` picker and guided remapping UI
    - PR3: #18595: Vim composer mode and the remap option
    
    ## Design Notes
    
    The new model resolves named actions into concrete runtime bindings once
    from config, then passes those bindings to the UI surfaces that handle
    input or render shortcut hints.
    
    The main concepts are:
    
    - **Context**: a scope where an action is active, such as `global`,
    `chat`, `composer`, `editor`, `pager`, `list`, or `approval`.
    - **Action**: a named operation inside a context, such as
    `global.open_transcript`, `composer.submit`, or `pager.close`.
    - **Binding**: one or more single-key shortcuts assigned to an action,
    written as config strings such as `ctrl-t`, `alt-backspace`, or
    `page-down`. Multi-step sequences such as `ctrl-x ctrl-s`, `g g`, or
    leader-key flows are not part of this PR.
    - **Resolution order**: context-specific config wins first, supported
    global fallbacks come next, and built-in defaults fill in anything
    unset.
    - **Explicit unbinding**: an empty array removes an action binding in
    that scope and does not fall through to a fallback binding.
    - **Conflict validation**: a resolved keymap rejects duplicate active
    bindings inside the same scope so one keypress cannot dispatch two
    actions.
    
    ## What Changed
    
    - Added `TuiKeymap` config support under `[tui.keymap]`, including typed
    contexts/actions, key alias normalization, generated schema coverage,
    and user-facing config errors.
    - Added `RuntimeKeymap` resolution in `codex-rs/tui/src/keymap.rs`,
    including fallback precedence, built-in defaults, explicit unbinding,
    and per-context conflict validation.
    - Rewired existing TUI handlers to consume resolved keymap actions
    instead of directly matching hard-coded keys in each component.
    - Updated key hint rendering and footer/pager/list surfaces so displayed
    shortcuts follow the resolved keymap.
    - Kept onboarding shortcuts fixed in
    `codex-rs/tui/src/onboarding/keys.rs` instead of exposing them through
    `[tui.keymap]`.
    
    ## Validation
    
    The branch includes focused coverage for config parsing, key
    normalization, runtime fallback resolution, explicit unbinding,
    duplicate-key conflict validation, default keymap consistency,
    onboarding startup key behavior, and UI hint snapshots affected by
    resolved key bindings.
  • feat: skip memory startup when Codex rate limits are low (#19990)
    ## Why
    
    Memory startup runs in the background after an eligible turn, but it can
    consume Codex backend quota at exactly the wrong time: when the user is
    already near a rate-limit boundary. This PR adds a guard so the memory
    pipeline backs off when the Codex rate-limit snapshot says the remaining
    budget is too low.
    
    ## What Changed
    
    - Added `memories.min_rate_limit_remaining_percent` with a default of
    `25`, clamped to `0..=100`, and regenerated `core/config.schema.json`.
    - Added `codex-rs/memories/write/src/guard.rs`, which fetches Codex
    backend rate limits before memory startup and skips phase 1 / phase 2
    when the Codex limit is reached or either tracked window is above the
    configured usage ceiling.
    - Keeps startup best-effort: non-Codex auth or rate-limit fetch/client
    failures preserve the existing memory startup behavior.
    - Records a `codex.memory.startup` counter with
    `status=skipped_rate_limit` when startup is skipped.
    - Added config parsing/clamping coverage and guard unit tests.
    
    ## Verification
    
    - Added `codex-rs/memories/write/src/guard_tests.rs` for threshold,
    primary/secondary window, and reached-limit behavior.
    - Added config tests for TOML parsing and clamping.
  • feat: trigger memories from user turns with cooldown (#19970)
    ## Why
    
    Memory startup was tied to thread lifecycle events such as create, load,
    and fork. That can run memory work before a thread receives real user
    input, and it makes startup cost scale with thread management instead of
    actual turns. Moving the trigger to `thread/sendInput` keeps memory
    startup aligned with the first real user turn and lets it use the
    current thread config at turn time.
    
    The idea is to prevent ghost cost due to pre-warm triggered by the app
    
    Turn-based startup can also make global phase-2 consolidation easier to
    request repeatedly, so this adds a success cooldown and tightens the
    default startup scan window.
    
    ## What Changed
    
    - Start `codex_memories_write::start_memories_startup_task` after a
    non-empty `thread/sendInput` turn is submitted, instead of from thread
    create/load/fork paths:
    https://github.com/openai/codex/blob/d4a6885b7829e2fd2ec7a09355e4f75ebe1d1fe3/codex-rs/app-server/src/codex_message_processor.rs#L6477-L6487
    - Expose `CodexThread::config()` so app-server can pass the live config
    into memory startup at turn time.
    - Add a six-hour successful-run cooldown for global phase-2
    consolidation via `SkippedCooldown`:
    https://github.com/openai/codex/blob/d4a6885b7829e2fd2ec7a09355e4f75ebe1d1fe3/codex-rs/state/src/runtime/memories.rs#L963-L966
    - Reduce memory startup defaults to at most 2 rollouts over 10 days:
    https://github.com/openai/codex/blob/d4a6885b7829e2fd2ec7a09355e4f75ebe1d1fe3/codex-rs/config/src/types.rs#L31-L34
    
    ## Verification
    
    Updated the memory runtime coverage around phase-2 reclaim behavior,
    including `phase2_global_lock_respects_success_cooldown`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Stabilize memory Phase 2 input ordering (#19967)
    ## Why
    
    Phase 2 still needs to choose the most relevant stage-1 memory outputs
    by usage and recency, but exposing that ranking as the rendered
    `raw_memories.md` order creates unnecessary large diff. Usage-count or
    timestamp changes can reshuffle otherwise unchanged memories, making the
    workspace diff noisy and giving the consolidation prompt a misleading
    recency signal from file position.
    This fix will reduce token consumption
    
    ## What Changed
    
    - Keep the existing top-N Phase 2 selection ranking by `usage_count`,
    `last_usage`, `source_updated_at`, and `thread_id`.
    - Return the selected rows in stable ascending `thread_id` order before
    syncing Phase 2 filesystem inputs.
    - Update the memory README, raw memories header, and consolidation
    prompt so they describe the stable order and tell the prompt to use
    metadata and workspace diffs instead of file order as the recency
    signal.
    - Adjust the memory runtime tests to use deterministic thread IDs and
    assert the stable return order separately from the ranked selection
    semantics.
    
    ## Test Coverage
    
    - Existing memory runtime tests in
    `codex-rs/state/src/runtime/memories.rs` now cover the stable returned
    ordering for Phase 2 inputs.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: split memories part 2 (#19860)
    Keep extracting memories out of core and moving the write trigger in the
    app-server
    This is temporary and it should move at the client level as a follow-up
    This makes core fully independant from `codex-memories-write`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add MultiAgentV2 root and subagent context hints (#19805)
    ## Why
    
    MultiAgentV2 sessions need startup guidance that matches the role of the
    thread that is actually being created. Root agents and subagents have
    different responsibilities, and forked subagents can inherit parent
    rollout history. If the parent hint is carried into the child context,
    the child can see stale or conflicting developer guidance before its own
    session-specific context is added.
    
    ## What changed
    
    - Added `features.multi_agent_v2.root_agent_usage_hint_text` and
    `features.multi_agent_v2.subagent_usage_hint_text` config fields,
    including schema/config parsing support.
    - Injected the matching root or subagent hint into the initial context
    as its own developer message when `multi_agent_v2` is enabled.
    - Filtered configured MultiAgentV2 usage-hint developer messages out of
    forked parent history so a child thread receives fresh guidance for its
    own session source/config.
    - Added targeted coverage for config parsing, initial-context rendering,
    feature-config deserialization, and forked-history filtering.
    
    ## Context examples
    
    With this config:
    
    ```toml
    [features.multi_agent_v2]
    enabled = true
    root_agent_usage_hint_text = "Root guidance."
    subagent_usage_hint_text = "Subagent guidance."
    ```
    
    A root thread initial context renders the root hint as a standalone
    developer message:
    
    ```text
    [developer]
    <existing developer context, when present>
    
    [developer]
    Root guidance.
    ```
    
    A subagent thread initial context renders the subagent hint instead:
    
    ```text
    [developer]
    <existing developer context, when present>
    
    [developer]
    Subagent guidance.
    ```
    
    When a subagent forks parent history, any parent developer message whose
    text exactly matches the configured MultiAgentV2 root or subagent hint
    is omitted from the forked history before the child receives its fresh
    subagent hint.
  • permissions: derive snapshot sandbox projections (#19775)
    ## Why
    
    `ThreadConfigSnapshot` is used by app-server and thread metadata code as
    a stable view of active runtime settings. Keeping both `sandbox_policy`
    and `permission_profile` in the snapshot duplicates permission state and
    makes it possible for the legacy projection to drift from the canonical
    profile.
    
    The legacy `sandbox` value is still needed at app-server compatibility
    boundaries, so this PR derives it on demand from the snapshot profile
    and cwd instead of storing it.
    
    ## What Changed
    
    - Removes `ThreadConfigSnapshot.sandbox_policy`.
    - Adds `ThreadConfigSnapshot::sandbox_policy()` as a compatibility
    projection from `permission_profile` plus `cwd`.
    - Updates app-server response/metadata code and tests to call the
    projection only where legacy fields still exist.
    - Keeps snapshot construction profile-only so split filesystem rules,
    disabled enforcement, and external enforcement remain represented by the
    canonical profile.
    
    ## Verification
    
    - `cargo test -p codex-app-server
    thread_response_permission_profile_preserves_enforcement --lib`
    - `cargo test -p codex-core
    dispatch_reclaims_stale_global_lock_and_starts_consolidation --lib`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19775).
    * #19900
    * #19899
    * #19776
    * __->__ #19775
  • permissions: make SessionConfigured profile-only (#19774)
    ## Why
    
    `SessionConfiguredEvent` is the internal event that tells clients what
    permissions are active for a session. Emitting both `sandbox_policy` and
    `permission_profile` leaves two possible authorities and forces every
    consumer to decide which one to honor. At this point in the migration,
    the profile is expressive enough to represent managed, disabled, and
    external sandbox enforcement, so the internal event can be profile-only.
    
    The wire compatibility concern is older serialized events or rollout
    data that only contain `sandbox_policy`; those still need to
    deserialize.
    
    ## What Changed
    
    - Removes `sandbox_policy` from `SessionConfiguredEvent` and makes
    `permission_profile` required.
    - Adds custom deserialization so old payloads with only `sandbox_policy`
    are upgraded to a cwd-anchored `PermissionProfile`.
    - Updates core event emission and TUI session handling to sync
    permissions from the profile directly.
    - Updates app-server response construction to derive the legacy
    `sandbox` response field from the active thread snapshot instead of from
    `SessionConfiguredEvent`.
    - Updates yolo-mode display logic to treat both
    `PermissionProfile::Disabled` and managed unrestricted filesystem plus
    enabled network as full-access, while still preserving the distinction
    between no sandbox and external sandboxing.
    
    ## Verification
    
    - `cargo test -p codex-protocol session_configured_event --lib`
    - `cargo test -p codex-protocol serialize_event --lib`
    - `cargo test -p codex-exec session_configured --lib`
    - `cargo test -p codex-app-server
    thread_response_permission_profile_preserves_enforcement --lib`
    - `cargo test -p codex-core
    session_configured_reports_permission_profile_for_external_sandbox
    --lib`
    - `cargo test -p codex-tui session_configured --lib`
    - `cargo test -p codex-tui
    yolo_mode_includes_managed_full_access_profiles --lib`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19774).
    * #19900
    * #19899
    * #19776
    * #19775
    * __->__ #19774
  • Avoid persisting ShutdownComplete after thread shutdown (#19630)
    ## Why
    
    Fixes #19475.
    
    `codex exec` can finish successfully and then emit an `ERROR` on stderr:
    
    ```text
    failed to record rollout items: thread <id> not found
    ```
    
    That happens because shutdown closes the live thread writer before
    emitting `ShutdownComplete`. The terminal event was still using the
    normal `send_event_raw` path, so it tried to append rollout items
    through a recorder that had already been removed. The answer is correct,
    but wrappers that treat stderr as failure can retry completed exec runs.
    
    This looks like a likely recent regression from
    [#18882](https://github.com/openai/codex/pull/18882), which routed live
    thread writes through `ThreadStore` and added the shutdown-time live
    writer close. I have not bisected this, so the PR treats #18882 as the
    likely source based on the affected shutdown code path rather than a
    proven first-bad commit.
    
    ## What Changed
    
    `ShutdownComplete` now bypasses rollout persistence after thread
    shutdown and is delivered directly to clients. The shutdown path still
    records the protocol event in the rollout trace before delivery,
    preserving trace visibility without attempting a post-shutdown
    thread-store append.
    
    The change also adds a regression test with the in-memory thread store
    to assert that shutdown creates and shuts down the live thread without
    appending another item after shutdown.
  • refactor: load agent identity runtime eagerly (#19763)
    ## Summary
    
    AgentIdentity auth previously registered the process task lazily behind
    a `OnceCell`. That meant the auth object could be constructed before its
    runtime task binding was known.
    
    This PR makes AgentIdentity auth load the runtime task at auth load time
    and stores the resulting process task id directly on the auth object.
    The model-provider call path can then read a concrete task id instead of
    handling a missing lazy value.
    
    ## Stack
    
    1. [refactor: make auth loading
    async](https://github.com/openai/codex/pull/19762) (merged)
    2. **This PR:** [refactor: load AgentIdentity runtime
    eagerly](https://github.com/openai/codex/pull/19763)
    3. [fix: configure AgentIdentity AuthAPI base
    URL](https://github.com/openai/codex/pull/19904)
    4. [feat: verify AgentIdentity JWTs with
    JWKS](https://github.com/openai/codex/pull/19764)
    
    ## Important call sites
    
    | Area | Change |
    | --- | --- |
    | `AgentIdentityAuth::load` | Registers the process task during auth
    loading and stores `process_task_id`. |
    | `CodexAuth::from_agent_identity_jwt` | Awaits AgentIdentity auth
    loading. |
    | model-provider auth | Reads a concrete `process_task_id` instead of an
    optional lazy value. |
    | AgentIdentity auth tests | Mock task registration now covers eager
    runtime allocation. |
    
    ## Design decisions
    
    AgentIdentity auth now treats task registration as part of constructing
    a usable auth object. That matches how callers use the value: once auth
    is present, the model-provider path expects the task-scoped assertion
    data to be ready.
    
    ## Testing
    
    Tests: targeted Rust auth test compilation, formatter, scoped Clippy
    fix, and Bazel lock check.
  • Remove ghost snapshots (#19481)
    ## Summary
    - Remove `ghost_snapshot` / `GhostCommit` from the Responses API surface
    and generated SDK/schema artifacts.
    - Keep legacy config loading compatible, but make undo a no-op that
    reports the feature is unavailable.
    - Clean up core history, compaction, telemetry, rollout, and tests to
    stop carrying ghost snapshot items.
    
    ## Testing
    - Unit tests passed for `codex-protocol`, `codex-core` targeted undo and
    compaction flows, `codex-rollout`, and `codex-app-server-protocol`.
    - Regenerated config and app-server schemas plus Python SDK artifacts
    and verified they match the checked-in outputs.
  • disallow fileparams metadata for custom mcps (#19836)
    ## Summary
    Disallow fileParams metadata for custom MCPs 
    
    Restricts Codex openai/fileParams handling to the first-party codex_apps
    MCP server. Custom MCP servers may still advertise the metadata, but
    Codex now ignores it for upload rewriting, preventing non-Apps tools
    from receiving signed OpenAI file refs for local paths. Added a
    regression test for the allowed and denied cases.
  • permissions: derive config defaults as profiles (#19772)
    ## Why
    
    This continues the permissions migration by making legacy config default
    resolution produce the canonical `PermissionProfile` first. The legacy
    `SandboxPolicy` projection should stay available at compatibility
    boundaries, but config loading should not create a legacy policy just to
    immediately convert it back into a profile.
    
    Specifically, when `default_permissions` is not specified in
    `config.toml`, instead of creating a `SandboxPolicy` in
    `codex-rs/core/src/config/mod.rs` and then trying to derive a
    `PermissionProfile` from it, we use `derive_permission_profile()` to
    create a more faithful `PermissionProfile` using the values of
    `ConfigToml` directly.
    
    This also keeps the existing behavior of `sandbox_workspace_write` and
    extra writable roots after #19841 replaced `:cwd` with `:project_roots`.
    Legacy workspace-write defaults are represented as symbolic
    `:project_roots` write access plus symbolic project-root metadata
    carveouts. Extra absolute writable roots are still added directly and
    continue to get concrete metadata protections for paths that exist under
    those roots.
    
    The platform sandboxes differ when a symbolic project-root subpath does
    not exist yet.
    
    * **Seatbelt** can encode literal/subpath exclusions directly, so macOS
    emits project-root metadata subpath policies even if `.git`, `.agents`,
    or `.codex` do not exist.
    * **bwrap** has to materialize bind-mount targets. Binding `/dev/null`
    to a missing `.git` can create a host-visible placeholder that changes
    Git repo discovery. Binding missing `.agents` would not affect Git
    discovery, but it would still create a host-visible project metadata
    placeholder from an automatic compatibility carveout. Linux therefore
    skips only missing automatic `.git` and `.agents` read-only metadata
    masks; missing `.codex` remains protected so first-time project config
    creation goes through the protected-path approval flow. User-authored
    `read` and `none` subpath rules keep normal bwrap behavior, and `none`
    can still mask the first missing component to prevent creation under
    writable roots.
    
    ## What Changed
    
    - Adds profile-native helpers for legacy workspace-write semantics,
    including `PermissionProfile::workspace_write_with()`,
    `FileSystemSandboxPolicy::workspace_write()`, and
    `FileSystemSandboxPolicy::with_additional_legacy_workspace_writable_roots()`.
    - Makes `FileSystemSandboxPolicy::workspace_write()` the single legacy
    workspace-write constructor so both `from_legacy_sandbox_policy()` and
    `From<&SandboxPolicy>` include the project-root metadata carveouts.
    - Removes the no-carveout `legacy_workspace_write_base_policy()` path
    and the `prune_read_entries_under_writable_roots()` cleanup that was
    only needed by that split construction.
    - Adds `ConfigToml::derive_permission_profile()` for legacy sandbox-mode
    fallback resolution; named `default_permissions` profiles continue
    through the permissions profile pipeline instead of being reconstructed
    from `sandbox_mode`.
    - Updates `Config::load()` to start from the derived profile, validate
    that it still has a legacy compatibility projection, and apply
    additional writable roots directly to managed workspace-write filesystem
    policies.
    - Updates Linux bwrap argument construction so missing automatic
    `.git`/`.agents` symbolic project-root read-only carveouts are skipped
    before emitting bind args; missing `.codex`, user-authored `read`/`none`
    subpath rules, and existing missing writable-root behavior are
    preserved.
    - Adds coverage that legacy workspace-write config produces symbolic
    project-root metadata carveouts, extra legacy workspace writable roots
    still protect existing metadata paths such as `.git`, and bwrap skips
    missing `.git`/`.agents` project-root carveouts while preserving missing
    `.codex` and user-authored missing subpath rules.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19772).
    * #19776
    * #19775
    * #19774
    * #19773
    * __->__ #19772
  • Show action required in terminal title (#18372)
    Implements #18162
    
    This updates the TUI terminal title to show an explicit action-required
    state when Codex is blocked on user approval or input. The terminal
    title now uses the activity title item to cover both active work and
    blocked-on-user states, while still accepting the legacy spinner config
    value.
    
    Changes
    - Rename the terminal title item from `spinner` to `activity` while
    preserving legacy config compatibility
    - Show `[ ! ] Action Required `while approval or input overlays are
    active, with a blinking `[ . ]` alternate state
    - Suppress the normal working spinner while Codex is blocked on user
    action
    - Add targeted coverage for action-required title behavior and legacy
    title-item parsing
    
    Testing
    - Trigger an approval or input modal and confirm the tab title
    alternates between `[ ! ] Action Required` and `[ . ] Action Required`
    - Disable the activity title item and confirm the action-required title
    does not appear
    - Resolve the prompt and confirm the title returns to the normal
    spinning/idel state
    
    
    https://github.com/user-attachments/assets/e9ecc530-a6be-4fd7-b9a6-d550a790eb2c
  • [codex] Trace cancelled inference streams (#19839)
    Records cancelled inference streams when Codex stops consuming a
    provider response before `response.completed`, preserving complete
    output items observed before cancellation.
    
    Also closes still-running inference calls when the owning turn ends, so
    reduced rollout traces do not leave stale `Running` inference nodes.
    
    Covered by focused reducer coverage and a core stream-drop test for
    partial output preservation.
  • permissions: remove cwd special path (#19841)
    ## Why
    
    The experimental `PermissionProfile` API had both `:cwd` and
    `:project_roots` special filesystem paths, which made the permission
    root ambiguous. This PR removes the unstable `current_working_directory`
    special path before the permissions API is stabilized, so callers use
    `:project_roots` for symbolic project-root access.
    
    ## What changed
    
    - Removes `FileSystemSpecialPath::CurrentWorkingDirectory` from protocol
    and app-server protocol models, plus regenerated app-server
    JSON/TypeScript schemas.
    - Replaces internal `:cwd` permission entries with `:project_roots`
    entries.
    - Keeps the existing cwd-update behavior for legacy-shaped
    workspace-write profiles, while removing the deleted
    `CurrentWorkingDirectory` case from that compatibility path.
    - Keeps `PermissionProfile::workspace_write()` as the reusable symbolic
    workspace-write helper, with docs noting that `:project_roots` entries
    resolve at enforcement time.
    - Updates app-server docs/examples and approval UI labeling to stop
    advertising `:cwd` as a permission token.
    
    ## Compatibility
    
    Persisted rollout items may contain the old
    `{"kind":"current_working_directory"}` tag from earlier experimental
    `permissionProfile` snapshots. This PR keeps that tag as a
    deserialize-only alias for `ProjectRoots { subpath: None }`, while
    continuing to serialize only the new `project_roots` tag.
    
    ## Follow-up
    
    This PR intentionally does not introduce an explicit project-root set on
    `SessionConfiguration` or runtime sandbox resolution. Today, the
    resolver still uses the active cwd as the single implicit project root.
    A follow-up should model project roots separately from tool cwd so
    `:project_roots` entries can resolve against the configured project
    roots, and resolve to no entries when there are no project roots.
    
    ## Verification
    
    - `cargo test -p codex-protocol permissions:: --lib`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-sandboxing -p codex-exec-server --lib`
    - `cargo test -p codex-core session_configuration_apply_ --lib`
    - `cargo test -p codex-app-server
    command_exec_permission_profile_project_roots_use_command_cwd --test
    all`
    - `cargo test -p codex-tui
    thread_read_session_state_does_not_reuse_primary_permission_profile
    --lib`
    - `cargo test -p codex-tui
    preset_matching_accepts_workspace_write_with_extra_roots --lib`
    - `cargo test -p codex-config --lib`
  • Cap original-detail image token estimates (#19865)
    Clamp original-detail image patch estimates to the current 10k patch
    budget so large images cannot inflate local context accounting without
    bound. Add regression coverage for an over-budget image.
    
    Fixes openai/codex#19806.
  • fix: filter dynamic deferred tools from model_visible_specs (#19771)
    fixes #19486
    
    ### Problem
    Right now dynamic deferred tools are filtered at normal-turn prompt
    building time, rather than upstream while building the `ToolRouter`
    itself. This causes issues because dynamic deferred tools are then
    wrongly included in the router's `model_visible_specs`, which is what
    the compaction request-building flow relies on.
    
    ### Fix
    Move the dynamic deferred tool filtering to `ToolRouter` creation time
    to solve this problem for every request that relies on `ToolRouter` for
    `model_visible_specs`, which solves the issue generically.
    
    ### Tests
    Added unit + integration tests to ensure dynamic deferred tools are
    omitted from `model_visible_specs` and compaction request respectively.
    
    Tested against live `/compact` endpoint; raw deferred dynamic tools
    without `tool_search` returned `400` (current bug), while the filtered
    payload (this fix) returns `200`.