130 Commits

  • [codex] consume pushed exec-server process events (#30273)
    ## Summary
    
    - complete unified-exec processes from the ordered event stream instead
    of issuing a final zero-wait `process/read`
    - add optional executor sandbox-denial state to `process/exited`
    - retain `process/read` as a retained-output and compatibility fallback
    for receiver lag, sequence gaps, and legacy servers
    - recover sandbox-denial state across transport reconnection
    - cover the real `TestCodex` remote-exec path without adding a public
    test-only event constructor
    
    ## Why
    
    A successful one-shot tool call currently receives its output and
    terminal notifications, then pays another wide-area `process/read` round
    trip before returning. Staging traces showed that remote response wait
    accounted for more than 99.8% of RPC time; local serialization,
    queueing, and deserialization were below 0.6 ms.
    
    ## Measured impact
    
    A direct staging A/B used the same build and route and changed only
    completion mode. Each arm ran three times with 30 one-shot
    `/usr/bin/true` calls per run. The table reports the median of the three
    per-run percentiles.
    
    | Metric | Final `process/read` | Pushed events | Change |
    | --- | ---: | ---: | ---: |
    | End-to-end completion p50 | 159.5 ms | 118.7 ms | -40.8 ms (-25.6%) |
    | End-to-end completion p95 | 182.4 ms | 131.7 ms | -50.6 ms (-27.8%) |
    | Completion-wait p50 | 80.1 ms | 41.5 ms | -38.5 ms (-48.1%) |
    | Final `process/read` RPC p50 | 79.9 ms | eliminated | -79.9 ms |
    
    TCP_NODELAY was enabled in both A/B arms, so its effect cancels out. The
    successful, complete, in-order event path issued zero final
    `process/read` calls.
    
    ## Compatibility and recovery
    
    - new servers send `sandboxDenied` on `process/exited`
    - legacy servers omit it, which triggers one compatibility
    `process/read`
    - broadcast lag or a sequence gap triggers a retained-output read
    - recovery remains bounded by the server's existing 1 MiB
    retained-output window
    - complete, in-order event streams issue no completion read
    - sandbox denial is attached to the exit event before consumers can
    observe process completion
    - server-first and client-first rollouts remain wire-compatible;
    server-first realizes the latency win immediately
    
    ## Integration coverage
    
    The `TestCodex` suite exercises four distinct remote-exec contracts:
    
    - complete pushed output/exit/close with zero reads
    - direct pushed sandbox denial with zero reads
    - legacy missing denial metadata with exactly one compatibility read
    - count-bounded replay eviction recovered from retained output without
    duplication
    
    ## Validation
    
    - `just test -p codex-core
    exec_command_consumes_pushed_remote_process_events`: 4 passed
    - `just test -p codex-core unified_exec::process_tests::`: 4 passed
    - `just test -p codex-exec-server`: 294 passed, 2 skipped
    - `just test -p codex-exec-server-protocol`: 5 passed
    - `just test -p codex-rmcp-client`: 89 passed, 2 skipped
    - focused Bazel `//codex-rs/core:core-all-test`: passed across 16 shards
    - scoped `just fix` passed for core and exec-server
    - `just fmt` passed
    
    The complete workspace suite was not rerun; focused Cargo and Bazel
    coverage passed for the changed behavior.
  • Shut down superseded MCP managers on refresh (#29608)
    ## Summary
    
    MCP refresh replaced the published connection manager without shutting
    down the manager it superseded. If another task retained that old
    manager, its stdio MCP processes stayed alive and accumulated across
    refreshes.
    
    Atomically swap in the refreshed manager, then explicitly shut down the
    exact manager returned by the swap. Add a process-level regression test
    that retains the old manager during refresh and verifies its stdio
    process exits while the replacement remains available.
    
    ## Context
    
    Explicit cleanup was lost when manager publication moved to `ArcSwap`.
    Dropping the old manager is not a reliable shutdown boundary because
    active callers can retain its `Arc` and underlying client process
    handles.
  • Propagate safety buffering events to app-server clients (#29371)
    Responses API safety buffering metadata currently stops at the transport
    boundary, so app-server clients cannot render the in-progress safety
    review state.
    
    This change:
    - decodes and deduplicates `safety_buffering` metadata from Responses
    API SSE and WebSocket events without suppressing the original response
    event
    - emits a typed core event containing the requested model plus backend
    use cases and reasons
    - forwards that event as `turn/safetyBuffering/updated` through
    app-server v2 and updates generated protocol schemas
    - keeps the side-channel event out of persisted rollouts and turn timing
    
    This supports the Codex Apps buffering UX and depends on the Responses
    API backend work in https://github.com/openai/openai/pull/1044569 and
    https://github.com/openai/openai/pull/1044571.
    
    Validation:
    - focused `codex-core` safety-buffering integration test passes
    - `cargo check -p codex-core -p codex-app-server -p
    codex-app-server-protocol`
    - `just fix -p codex-api -p codex-protocol -p codex-core -p
    codex-app-server-protocol -p codex-app-server -p codex-rollout -p
    codex-rollout-trace -p codex-otel`
    - `just fmt`
    - broad package test run: 4,430/4,492 passed; 62 unrelated
    local-environment/concurrency failures involved unavailable test
    binaries, MCP subprocess setup, and app-server timeouts
  • Scope network approvals by environment (#28899)
    Stacked on #28766.
    
    ## Why
    
    Network approvals are environment-scoped: allowing a host in one
    execution environment should not allow the same host in another
    environment.
    
    #28766 adds the inert IDs and constructor plumbing. This PR applies the
    behavior on top.
    
    ## What changed
    
    - Route managed network traffic through per-environment HTTP and SOCKS
    proxy listeners.
    - Stamp HTTP, HTTPS CONNECT, SOCKS TCP, and SOCKS UDP policy requests
    with the source environment at the proxy boundary.
    - Carry the selected execution environment through shell, unified exec,
    zsh-fork, and sandbox transform paths.
    - Include the environment in pending, approved-for-session, and
    denied-for-session network approval cache keys.
    - Include the environment in approval IDs and approval prompts.
    - Preserve legacy fallback for unattributed requests, but deny when
    active-call attribution is ambiguous.
    - Fail closed if an environment-specific proxy endpoint cannot be
    prepared.
    
    ## Validation
    
    - just fmt
    - CI will run tests and clippy
  • Add per-turn multi-agent mode (#28685)
    ## Why
    
    Multi-agent v2 currently carries an explicit-request-only delegation
    rule in its static usage hint. That provides a safe default, but it
    prevents clients from selecting proactive delegation per turn without
    changing static guidance or rewriting prior model context.
    
    This change makes delegation mode a session selection that can be
    updated through `turn/start`, while deriving the effective model-visible
    mode separately for each turn. Eligible multi-agent v2 turns remain
    explicit-request-only unless proactive mode is both selected and
    enabled.
    
    ## What changed
    
    - Add the experimental `turn/start.multiAgentMode` parameter with
    `explicitRequestOnly` and `proactive` values. Omission retains the
    loaded session's current optional selection.
    - Add the default-off `features.multi_agent_mode` feature gate. Eligible
    multi-agent v2 turns use the selected mode when enabled; an unset
    selection or disabled gate resolves to `explicitRequestOnly`.
    - Treat mode prompting as inapplicable for multi-agent v1 and other
    unsupported session configurations, producing no multi-agent mode
    developer message rather than rejecting the turn.
    - Move the explicit-request-only rule out of the static v2 usage hint
    and into a bounded, tagged developer context fragment.
    - Emit the effective mode in initial context and only when that
    effective mode changes on later turns.
    - Persist the effective mode in `TurnContextItem` as the durable
    baseline for resume and context-update comparisons.
    
    Historical rollout items are not rewritten. Later mode developer
    messages establish the current rule incrementally.
    
    ## Not covered
    
    - Initial selection through `thread/start` and selected-mode reporting
    from thread lifecycle/settings APIs; those are isolated in the stacked
    #28792.
    - A TUI control or slash command for selecting the mode.
    - Persisting a preferred mode to `config.toml`; selection remains
    session/turn scoped.
    - Changes to multi-agent concurrency limits, tool availability, or model
    catalog capability declarations.
    - Rewriting historical rollout prompt items. Cold resume restores the
    latest persisted effective mode when available while leaving historical
    developer messages intact.
    
    ## Verification
    
    - `CARGO_INCREMENTAL=0 just test -p codex-core multi_agent_mode`
    - Focused app-server coverage verifies that `turn/start.multiAgentMode`
    produces proactive developer instructions for an eligible v2 turn.
    
    ## Stack
    
    Followed by #28792, which adds `thread/start` initialization and
    lifecycle/settings observability.
  • [codex] Remove child AGENTS.md prompt experiment (#28993)
    ## Why
    
    `child_agents_md` is a disabled, under-development experiment that adds
    a second model-visible explanation of hierarchical `AGENTS.md` behavior.
    Keeping it leaves unused prompt, configuration, documentation, and test
    surface.
    
    ## What changed
    
    - remove the `ChildAgentsMd` feature and `child_agents_md` config schema
    entry
    - remove the hierarchical prompt asset, export, and instruction
    injection
    - remove feature-specific tests and documentation
    - keep the generic unstable-feature warning coverage using
    `apply_patch_streaming_events`
    
    Normal project `AGENTS.md` discovery and composition are unchanged.
    
    ## Testing
    
    - `just test -p codex-features`
    - `just test -p codex-prompts`
    - `just test -p codex-core agents_md`
    - `just test -p codex-core unstable_features_warning`
  • current time reminders impl for system clock (varlatency 2/n) (#28824)
    Stacked on #28822.
    
    ## Summary
    
    - add a host-injectable current-time provider with a built-in system
    implementation
    - record UTC developer reminders in history immediately before due model
    requests
    - keep cadence state per session and force a refresh after compaction
    
    This does NOT include the app server client <-> server clock logic. This
    PR is only for the reminder message & system clock that will be used in
    prod.
    
    ## Testing
    
    - `just test -p codex-core varlatency_`
    - `just clippy -p codex-core -p codex-app-server -p codex-mcp-server -p
    codex-thread-manager-sample`
    - `just fmt`
  • [codex] rollout budget implementation (varlength 2/N) (#28494)
    ## Stack
    
    Depends on #28746. This PR implements shared rollout-budget accounting
    and model-visible reminders using the configuration defined in #28746.
    
    # Description / Main changes to Core:
    
    `AgentControl` will now be the area where "rollout level" features &
    accounting will have to live. It is incorrectly named for this
    responsibility, but I think it can hold all the necessary shared state &
    features (rollout token budget, mutliple thread interruption
    responsibilitym etc)
    
    In this PR, we have one "token ledger" that each thread will subtract
    from when sampling. The "charge" will occur when response.completed() is
    done and the calculation will be done on the responses api usage
    carrier. The calculation will weigh sampling and pre-fill tokens as
    specified.
    
    Every time the budget crosses the configured reminder threshold, a
    developer message is appended before the thread's next request
    
    This remaining budget will _always_ be restated/reminded after a
    compaction event.
    
    Expiration and fan-out interruption will be in the stacked follow-up
    (and also live in Agent Control).
    
    ## Reminders
    
    "You have weighted {session_tokens_left} tokens left in the shared
    session token budget."
    
    The first request in each thread context receives the current remainder.
    Later reminders are emitted after aggregate weighted usage crosses a
    configured interval. If several intervals are crossed before a thread
    sends another request, Core inserts one reminder with the latest
    remainder.
    
    Compaction response usage is charged before the next context starts. The
    next reminder is appended after the compaction summary, leaving the
    initial context content stable.
    
    ## Tests
    
    Integration coverage verifies:
    
    - weighted output and non-cached input accounting
    - initial and periodic reminders
    - shared accounting between a root and sub-agent
    - post-compaction remainder and message placement
    
    Local checks:
    
    - `just fmt`
    - `just test -p codex-core rollout_budget`
    - `git diff --check`
    
    The full workspace test suite was not run locally.
  • Expose selecte namespaces as direct model tools (#28825)
    ## Why
    
    Som tools, such as history and notes, must remain top-level when MCP
    deferral is enabled while staying unavailable through code-mode `exec`.
    
    ## What changed
    
    - Added `features.code_mode.direct_only_tool_namespaces`.
    - Classified matching MCP tools as `DirectModelOnly`.
    - Kept those tools top-level in `code_mode_only`.
    - Excluded them from `tool_search` deferral and the nested `exec`
    surface.
    - Updated the generated config schema.
    
    ## Validation
    
    - `code_mode_only_exposes_direct_model_only_mcp_namespaces`
    - `load_config_resolves_code_mode_config`
  • Route image extension reads through turn environments v2 (#27498)
    ## Why
    
    Image generation used `std::fs::read` for referenced image paths, which
    did not support environment-backed filesystems or their sandbox context.
    
    ## What changed
    
    - Expose optional turn environments to extension tool calls.
    - Include each environment’s ID, working directory, filesystem, and
    sandbox context.
    - Read referenced images through the selected environment filesystem.
    - Keep sandbox usage at the extension call site so extensions can choose
    the appropriate access mode.
    - Consolidate image request construction into one async function.
    - Add coverage for successful environment reads and read failures.
    
    ## Validation
    
    - `cargo check -p codex-image-generation-extension --tests`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    `just test -p codex-image-generation-extension` could not complete
    because the build exhausted available disk space.
  • [codex] Add token budget context feature (#27438)
    ## Why
    
    The model should be able to see bounded context-window budget metadata
    when the `token_budget` feature is enabled. The full-window message is
    only injected with full context, while normal turns get a smaller
    follow-up only when reported usage first crosses a budget threshold.
    
    ## What changed
    
    - Added the `TokenBudget` feature flag.
    - Added `<token_budget>` developer fragments for full context-window
    metadata and current-window remaining tokens.
    - Inserted the threshold message during normal turn handling by
    comparing token usage before and after sampling, avoiding persistent
    threshold bookkeeping.
    - Added core integration coverage for full-context-only metadata and
    25/50/75 percent threshold messages.
    
    ## Verification
    
    - `just test -p codex-core token_budget`
    - `git diff --check`
  • feat: count V2 concurrency by active execution (#26969)
    ## Why
    
    Multi-Agent V2 concurrency should count active non-root turns, not
    resident or durable agent threads. The limit is intentionally best
    effort: admission checks are synchronous, but concurrent successful
    checks may overshoot slightly.
    
    ## What changed
    
    - Keep one root-derived execution limit on the shared `AgentControl`.
    - Count active V2 subagent turns with an RAII guard owned by
    `RunningTask`.
    - Check capacity before spawning or starting an idle agent, including
    direct app-server `turn/start` submissions.
    - Preserve queued delivery for agents that are already running.
    - Exempt automatic idle continuations so `/goal` work is not dropped
    when capacity is temporarily full.
    - Keep root and V1 turns outside this limiter.
    
    ## Test coverage
    
    - `execution_guards_count_active_v2_subagent_turns`
    - `execution_guards_ignore_root_and_v1_turns`
    - `v2_nested_spawn_checks_shared_active_execution_capacity`
  • fix: preserve approval sandbox decisions in unified exec (#24981)
    ## Why
    
    This PR fixes approval sandbox semantics in the unified-exec path. The
    zsh-fork runtime exposed the bug because the shell can do meaningful
    work before any intercepted child `execv(2)` exists: redirections,
    builtins, globbing, and pipeline setup all happen in the launch process.
    If the model requested `sandbox_permissions=require_escalated`, or an
    exec-policy `allow` rule explicitly bypassed the sandbox, that approved
    sandbox decision needs to be preserved for the launch path and for
    intercepted execs that use the same approval machinery.
    
    The behavior is not only about zsh fork. The production changes are in
    shared approval/escalation code, so they also affect non-zsh-fork
    intercepted exec paths that go through the same sandbox decision logic.
    The narrow intent is to preserve the approval decision while still
    keeping denied-read profiles and bounded additional-permission requests
    sandboxed.
    
    ## Production Changes
    
    - `codex-rs/core/src/tools/runtimes/unified_exec.rs`: derives a
    `launch_sandbox_permissions` value from the requested sandbox
    permissions and the runtime filesystem policy, then uses that value for
    managed-network/env setup and launch sandbox selection. This keeps full
    approval or policy-bypass decisions visible to the first unified-exec
    attempt, while still preventing a full sandbox override from discarding
    denied-read restrictions. Direct unified exec keeps the same decision
    surface; the important difference is that zsh-fork launch setup no
    longer accidentally loses the approved parent sandbox decision.
    
    - `codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs`: makes
    intercepted-exec escalation selection explicit for the three sandbox
    permission modes. `UseDefault` only escalates when an exec-policy
    decision allows sandbox bypass, `RequireEscalated` escalates when
    unsandboxed execution is allowed, and `WithAdditionalPermissions`
    escalates through the bounded additional-permissions path instead of
    being treated as a full unsandboxed override. Unsandboxed intercepted
    execs now also rebuild the environment as `RequireEscalated`, which
    strips managed-network proxy variables consistently with other
    unsandboxed execution.
    
    ## Test Coverage
    
    Most of the PR is tests. The new coverage verifies:
    
    - unified exec preserves parent approval and exec-policy sandbox
    decisions for zsh-fork launch selection;
    - bounded `with_additional_permissions` remains sandboxed and
    permission-profile based;
    - denied-read profiles are not weakened by parent approval;
    - explicit prompt rules still prompt for intercepted execs after the
    parent command is approved;
    - unsandboxed intercepted execs strip managed-network env vars.
    
    No documentation update is needed; this is an internal approval/sandbox
    correctness fix.
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/24981).
    * #24982
    * __->__ #24981
  • [codex] Use standalone tools for Responses Lite (#26490)
    ## Summary
    
    Responses Lite does not execute hosted Responses tools, so models using
    it must route web search and image generation through Codex-owned
    executors & standalone Response's API endpoints.
    
    This PR is stacked on #26487.
    
    ## Validation
    
    - `cargo test -p codex-core responses_lite_ --lib`
    - `cargo test -p codex-core
    standalone_executors_remain_hidden_without_flags_or_responses_lite
    --lib`
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates --lib`
    - `cargo test -p codex-web-search-extension -p
    codex-image-generation-extension`
    - `cargo test -p codex-app-server --test all standalone_`
    - `cargo fmt --all -- --check`
  • [codex-rs] auto-review model override (#23767)
    ## Why
    
    Guardian auto-review normally uses the provider-preferred review model
    when one is available. Some parent models need model-catalog metadata to
    select a different review model while keeping older `/models` payloads
    compatible when that metadata is absent.
    
    ## What changed
    
    - Added optional `ModelInfo::auto_review_model_override` metadata to the
    public model payload as a review-model slug.
    - Updated Guardian review model selection to prefer the catalog override
    when present, while preserving the existing provider preferred-model
    path and parent-model fallback when it is omitted.
    - Added focused Guardian coverage for override and no-override model
    selection.
    - Added an `auto_review` core integration suite test that loads override
    metadata from a remote model catalog path and asserts the strict
    auto-review `/responses` request uses the catalog-selected review model.
    - Updated existing `ModelInfo` fixtures and local catalog constructors
    for the new optional field.
    
    ## Validation
    
    - `cargo test -p codex-protocol
    model_info_defaults_availability_nux_to_none_when_omitted`
    - `cargo test -p codex-core guardian_review_uses_`
    - `cargo test -p codex-core
    remote_model_override_uses_catalog_model_for_strict_auto_review --test
    all`
    - `just fix -p codex-protocol`
    - `just fix -p codex-core`
    - `just fmt`
    - `git diff --check`
  • [codex] Add model tool mode selector (#25031)
    ## Why
    Some models need to select their code-execution behavior through model
    catalog metadata. Models without that metadata must continue to follow
    the existing `CodeMode` and `CodeModeOnly` feature flags, including when
    a newer server sends an enum value this client does not recognize.
    
    ## What changed
    - add optional `ModelInfo.tool_mode` metadata with `direct`,
    `code_mode`, and `code_mode_only`
    - treat omitted and unknown wire values as `None`
    - resolve `None` from the existing feature flags
    - carry the resolved `ToolMode` directly on `TurnContext`, outside
    `Config`
    - use the resolved value for turn creation, model switches, review
    turns, tool planning, and code execution
    
    ## Coverage
    - add protocol coverage for omitted, known, and unknown enum values
    - add focused coverage for flag fallback and explicit metadata
    overriding feature flags
    - add core integration coverage that fetches remote model metadata
    through `/v1/models` and verifies the outbound `/responses` tools for
    explicit `direct` and `code_mode_only` selectors
    
    ## Stack
    - followed by #25032
  • fix(auto-review) skip legacy notify for auto review threads (#24714)
    ## Summary
    Clear inherited legacy `notify` from Guardian review session config,
    since we should not be passing auto review threads into `notify`
    targets. Keeps legacy notify payload and hook runtime behavior unchanged
    for normal user turns.
    
    ## Testing
    - [x] add a Guardian config regression and dedicated Guardian
    integration test so review sessions cannot inherit parent notify hooks
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • Run compact hooks for remote compaction v2 (#22828)
    ## Why
    
    Remote compaction v2 is the `/responses` implementation of
    session-history compaction, but it still needs to preserve the
    observable contract of the legacy `/responses/compact` path. In
    particular, users and integrations that rely on `PreCompact` and
    `PostCompact` hooks should not see different behavior when
    `remote_compaction_v2` is enabled.
    
    ## What Changed
    
    - Runs `PreCompact` before issuing the remote compaction v2 request,
    including `Interrupted` analytics when a pre-hook stops execution.
    - Runs `PostCompact` after a successful v2 compaction and aborts the
    turn if the post-hook stops execution.
    - Adds `compact_remote_parity` coverage that compares legacy and v2
    compaction across manual transcript shapes, automatic pre-turn
    compaction, automatic mid-turn compaction, hook payloads, replacement
    history, follow-up request payloads, and API-key `service_tier=fast`
    behavior.
    - Registers the new parity suite under `core/tests/suite`.
    
    Relevant code:
    
    -
    [`compact_remote_v2.rs`](https://github.com/openai/codex/blob/af63745cb502183a6fc447d0240f8150934d70b7/codex-rs/core/src/compact_remote_v2.rs)
    -
    [`compact_remote_parity.rs`](https://github.com/openai/codex/blob/af63745cb502183a6fc447d0240f8150934d70b7/codex-rs/core/tests/suite/compact_remote_parity.rs)
    
    ## Verification
    
    - Added `core/tests/suite/compact_remote_parity.rs` to assert parity
    between legacy remote compaction and remote compaction v2 for the
    affected request, hook, rollout-history, and follow-up paths.
    - Existing `compact_remote_v2` unit coverage still exercises v2
    replacement-history retention and compaction-output collection.
  • Add user_input_requested_during_turn to MCP turn metadata (#22237)
    ## Why
    - Similar change as https://github.com/openai/codex/pull/21219
    - Without change: MCP tool calls receive
    `_meta["x-codex-turn-metadata"]` with various key values.
    - Issue: MCP servers currently do not know if user input was requested
    during the turn (Ex: Model decides to prompt the user for approval
    mid-turn before making a possibly risky tool call). MCP servers may want
    to know this when tracking latency metrics because these instances are
    inflated.
    
    ## What Changed
    - With change: MCP turn metadata now includes
    `user_input_requested_during_turn` when a model-visible
    `request_user_input` call happened earlier in the turn, propagated in
    `_meta["x-codex-turn-metadata"]`.
    - `mark_turn_user_input_requested()` is called when user input is
    requested through either MCP elicitation (`mcp.rs`) or the
    `request_user_input` tool (`mod.rs`).
    - MCP tool call `_meta` is now built immediately before execution
    (`mcp_tool_call.rs`) so user input requested earlier in the same turn,
    including within the same tool call via elicitation, is reflected in the
    metadata.
    - Normal `/responses` turn metadata headers are unchanged.
    
    ## Verification
    - `codex-rs/core/src/session/mcp_tests.rs`
    - `codex-rs/core/src/tools/handlers/request_user_input_tests.rs`
    - `codex-rs/core/src/turn_metadata_tests.rs`
    - `codex-rs/core/tests/suite/search_tool.rs`
  • feat(sandbox): add Windows deny-read parity (#18202)
    ## Why
    
    The split filesystem policy stack already supports exact and glob
    `access = none` read restrictions on macOS and Linux. Windows still
    needed subprocess handling for those deny-read policies without claiming
    enforcement from a backend that cannot provide it.
    
    ## Key finding
    
    The unelevated restricted-token backend cannot safely enforce deny-read
    overlays. Its `WRITE_RESTRICTED` token model is authoritative for write
    checks, not read denials, so this PR intentionally fails that backend
    closed when deny-read overrides are present instead of claiming
    unsupported enforcement.
    
    ## What changed
    
    This PR adds the Windows deny-read enforcement layer and makes the
    backend split explicit:
    
    - Resolves Windows deny-read filesystem policy entries into concrete ACL
    targets.
    - Preserves exact missing paths so they can be materialized and denied
    before an enforceable sandboxed process starts.
    - Snapshot-expands existing glob matches into ACL targets for Windows
    subprocess enforcement.
    - Honors `glob_scan_max_depth` when expanding Windows deny-read globs.
    - Plans both the configured lexical path and the canonical target for
    existing paths so reparse-point aliases are covered.
    - Threads deny-read overrides through the elevated/logon-user Windows
    sandbox backend and unified exec.
    - Applies elevated deny-read ACLs synchronously before command launch
    rather than delegating them to the background read-grant helper.
    - Reconciles persistent deny-read ACEs per sandbox principal so policy
    changes do not leave stale deny-read ACLs behind.
    - Fails closed on the unelevated restricted-token backend when deny-read
    overrides are present, because its `WRITE_RESTRICTED` token model is not
    authoritative for read denials.
    
    ## Landed prerequisites
    
    These prerequisite PRs are already on `main`:
    
    1. #15979 `feat(permissions): add glob deny-read policy support`
    2. #18096 `feat(sandbox): add glob deny-read platform enforcement`
    3. #17740 `feat(config): support managed deny-read requirements`
    
    This PR targets `main` directly and contains only the Windows deny-read
    enforcement layer.
    
    ## Implementation notes
    
    - Exact deny-read paths remain enforceable on the elevated path even
    when they do not exist yet: Windows materializes the missing path before
    applying the deny ACE, so the sandboxed command cannot create and read
    it during the same run.
    - Existing exact deny paths are preserved lexically until the ACL
    planner, which then adds the canonical target as a second ACL target
    when needed. That keeps both the configured alias and the resolved
    object covered.
    - Windows ACLs do not consume Codex glob syntax directly, so glob
    deny-read entries are expanded to the concrete matches that exist before
    process launch.
    - Glob traversal deduplicates directory visits within each pattern walk
    to avoid cycles, without collapsing distinct lexical roots that happen
    to resolve to the same target.
    - Persistent deny-read ACL state is keyed by sandbox principal SID, so
    cleanup only removes ACEs owned by the same backend principal.
    - Deny-read ACEs are fail-closed on the elevated path: setup aborts if
    mandatory deny-read ACL application fails.
    - Unelevated restricted-token sessions reject deny-read overrides early
    instead of running with a silently unenforceable read policy.
    
    ## Verification
    
    - `cargo test -p codex-core
    windows_restricted_token_rejects_unreadable_split_carveouts`
    - `just fmt`
    - `just fix -p codex-core`
    - `just fix -p codex-windows-sandbox`
    - GitHub Actions rerun is in progress on the pushed head.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Reapply "Move skills watcher to app-server" (#21652)
    ## Why
    
    PR #21460 reverted the earlier move of skills change watching from
    `codex-core` into app-server. This reapplies that boundary change so
    app-server owns client-facing `skills/changed` notifications and core no
    longer carries the watcher.
    
    ## What
    
    - Restore the app-server `SkillsWatcher` and register it from thread
    listener setup.
    - Remove the core-owned skills watcher and its core live-reload
    integration surface.
    - Restore app-server coverage for `skills/changed` notifications after a
    watched skill file changes.
    
    ## Validation
    
    - `cargo test -p codex-app-server --test all
    suite::v2::skills_list::skills_changed_notification_is_emitted_after_skill_change
    -- --exact --nocapture`
    - `cargo test -p codex-core --lib --no-run`
  • Enable --deny-warnings for cargo shear (#21616)
    ## Summary
    
    In https://github.com/openai/codex/pull/21584, we disabled doctests for
    crates that lack any doctests. We can enforce that property via `cargo
    shear --deny-warnings`: crates that lack doctests will be flagged if
    doctests are enabled, and crates with doctests will be flagged if
    doctests are disabled.
    
    A few additional notes:
    
    - By adding `--deny-warnings`, `cargo shear` also flagged a number of
    modules that were not reachable at all. Some of those have been removed.
    - This PR removes a usage of `windows_modules!` (since `cargo shear` and
    `rustfmt` couldn't see through it) in favor of simple `#[cfg(target_os =
    "windows")]` macros. As a consequence, many of these files exhibit churn
    in this PR, since they weren't being formatted by `rustfmt` at all on
    main.
    - Again, to make the code more analyzable, this PR also removes some
    usages of `#[path = "cwd_junction.rs"]` in favor of a more standard
    module structure. The bin sidecar structure is still retained, but,
    e.g., `windows-sandbox-rs/src/bin/command_runner.rs‎` was moved to
    `windows-sandbox-rs/src/bin/command_runner/main.rs`, and so on.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Move skills watcher to app-server (#21287)
    ## Why
    
    Skills update notifications are app-server API behavior, but the watcher
    lived in `codex-core` and surfaced through
    `EventMsg::SkillsUpdateAvailable`. Moving the watcher out keeps core
    focused on thread execution and lets app-server own both cache
    invalidation and the `skills/changed` notification.
    
    ## What changed
    
    - Added an app-server-owned skills watcher that watches local skill
    roots, clears the shared skills cache, and emits `skills/changed`
    directly.
    - Registers skill watches from the common app-server thread listener
    attach path, including direct starts, resumes, and app-server-observed
    child or forked threads.
    - Stores the `WatchRegistration` on `ThreadState`, so listener
    replacement, thread teardown, idle unload, and app-server shutdown
    deregister by dropping the RAII guard.
    - Removed `EventMsg::SkillsUpdateAvailable`, the core watcher, and the
    old core live-reload test.
    - Extended the app-server skills change test to verify a cached skills
    list is refreshed after a filesystem change without forcing reload.
    
    ## Validation
    
    - `cargo check -p codex-core -p codex-app-server -p codex-mcp-server -p
    codex-rollout -p codex-rollout-trace`
    - `cargo test -p codex-app-server
    skills_changed_notification_is_emitted_after_skill_change`
  • core: fix apply_patch request permissions test (#21060)
    ## Why
    
    The Bazel test coverage change exposed
    `approved_folder_write_request_permissions_unblocks_later_apply_patch`,
    and `rust-ci-full.yml` showed the same test failing on `main` on macOS.
    There were two separate classes of problems here.
    
    ### Clean CI failure
    
    The test emits an `apply_patch` tool call, but its config did not enable
    the `apply_patch` tool, so the mocked response completed without an
    `apply-patch-call` output. After enabling the tool, the same path also
    needs the aggregate `codex-core` test binary to dispatch
    `--codex-run-as-fs-helper`; sandboxed `apply_patch` uses that helper
    under macOS Seatbelt.
    
    The test now also canonicalizes the temporary patch target before
    building the patch payload so the path matches normalized grants on
    macOS, where `/var` paths often normalize to `/private/var`.
    
    ### Local/enterprise config isolation
    
    The core test harness now builds its default test config with managed
    config disabled, so host-managed enterprise config cannot alter these
    tests. The request-permissions turns in this test also explicitly use
    the user reviewer path, keeping the assertions focused on
    `request_permissions` behavior rather than reviewer defaults from the
    host.
    
    ## What Changed
    
    - Enable `apply_patch` in
    `approved_folder_write_request_permissions_unblocks_later_apply_patch`.
    - Teach the core integration test binary to dispatch
    `CODEX_FS_HELPER_ARG1`, matching the existing apply-patch and
    linux-sandbox dispatch paths.
    - Canonicalize the tempdir-backed patch target before creating the
    patch.
    - Ignore managed config in default core test configs and explicitly pin
    this test to `ApprovalsReviewer::User`.
    
    ## Verification
    
    Run outside the Codex app sandbox because these macOS tests
    intentionally spawn Seatbelt:
    
    - `cargo test -p codex-core
    approved_folder_write_request_permissions_unblocks_later_apply_patch`
    - `cargo test -p codex-core
    approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args`
  • [tool_suggest] More prompt polishes. (#20566)
    Tool suggest still misfires when model needs tool_search, updating the
    prompts to further disambiguate it:
    
    - [x] rename it from `tool_suggest` to `request_plugin_install`
    - [x] rephrase "suggestion" to "install" in the tool descriptions.
    - [x] disambiguate "the tool" vs "the plugin/connector". 
    
    Tested with the Codex App and verified it still works.
  • [codex] Remove unused event messages (#20511)
    ## Why
    
    Several legacy `EventMsg` variants were still emitted or mapped even
    though clients either ignored them or had moved to item/lifecycle
    events. `Op::Undo` had also degraded to an unavailable shim, so this
    removes that dead task path instead of preserving a command that cannot
    do useful work.
    
    `McpStartupComplete`, `WebSearchBegin`, and `ImageGenerationBegin` are
    intentionally kept because useful consumers still depend on them: MCP
    startup completion drives readiness behavior, and the begin events let
    app-server/core consumers surface in-progress web-search and
    image-generation items before the final payload arrives.
    
    ## What Changed
    
    - Removed weak legacy event variants and payloads from `codex-protocol`,
    including legacy agent deltas, background events, and undo lifecycle
    events.
    - Kept/restored `EventMsg::McpStartupComplete`,
    `EventMsg::WebSearchBegin`, and `EventMsg::ImageGenerationBegin` with
    serializer and emission coverage.
    - Updated core, rollout, MCP server, app-server thread history,
    review/delegate filtering, and tests to rely on the useful replacement
    events that remain.
    - Removed `Op::Undo`, `UndoTask`, the undo test module, and stale TUI
    slash-command comments.
    - Stopped agent job/background progress and compaction retry notices
    from emitting `BackgroundEvent` payloads.
    
    ## Verification
    
    - `cargo check -p codex-protocol -p codex-app-server-protocol -p
    codex-core -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-protocol -p codex-app-server-protocol -p
    codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - `cargo test -p codex-core --test all suite::items`
    - `just fix -p codex-protocol -p codex-app-server-protocol -p codex-core
    -p codex-rollout -p codex-rollout-trace -p codex-mcp-server`
    - Earlier coverage on this PR also included `codex-mcp`, `codex-tui`,
    core library tests, MCP/plugin/delegate/review/agent job tests, and MCP
    startup TUI tests.
  • 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>
  • Support MCP tools in hooks (#18385)
    ## Summary
    
    Lifecycle hooks currently treat `PreToolUse`, `PostToolUse`, and
    `PermissionRequest` as Bash-only flows
    - hook schema constrains `tool_name` to `Bash`
    - hook input assumes a command-shaped `tool_input`
    - core hook dispatch path passes only shell command strings
    
    That means hooks cannot target MCP tools even though MCP tool names are
    model-visible and stable
    
    This change generalizes those hook paths so they can match and receive
    payloads for MCP tools while preserving the existing Bash behavior.
    
    ## Reviewer Notes
    
    I think these are the key files
    - `codex-rs/core/src/tools/handlers/mcp.rs`
    - `codex-rs/core/src/mcp_tool_call.rs`
    
    Otherwise the changes across apply_patch, shell, and unified_exec are
    mainly to rewire everything to be `tool_input` based instead of just
    `command` so that it'll make sense for MCP tools.
    
    ## Changes
    
    - Allow `PreToolUse`, `PostToolUse`, and `PermissionRequest` hook inputs
    to carry arbitrary `tool_name` and `tool_input` values instead of
    hard-coding `Bash` and command-only payloads.
    - Add MCP hook payload support through `McpHandler`, using the
    model-visible tool name from `ToolInvocation` and the raw MCP arguments
    as `tool_input`.
    - Include MCP tool responses in `PostToolUse` by serializing
    `McpToolOutput` into the hook response payload.
    - Run `PermissionRequest` hooks for MCP approval requests after
    remembered approval checks and before falling back to user-facing MCP
    elicitation.
    - Preserve exact matching for literal hook matchers like `Bash` and
    `mcp__memory__create_entities`, while keeping regex matcher support for
    patterns like `mcp__memory__.*` and `mcp__.*__write.*`.
    
    ---------
    
    Co-authored-by: Andrei Eternal <eternal@openai.com>
    Co-authored-by: Codex <noreply@openai.com>
  • test(core): move prompt debug coverage to integration suite (#18916)
    ## Why
    
    `build_prompt_input` now initializes `ExecServerRuntimePaths`, which
    requires a configured Codex executable path. The previous inline unit
    test in `core/src/prompt_debug.rs` built a bare `test_config()` and then
    failed before it could assert anything useful:
    
    ```text
    Codex executable path is not configured
    ```
    
    This coverage is also integration-shaped: it drives the public
    `build_prompt_input` entry point through config, thread, and session
    setup rather than testing a small internal helper in isolation.
    
    Bazel CI did not catch this earlier because the affected test was behind
    the same wrapped Rust unit-test path fixed by #18913. Before that
    launcher/sharding fix, the outer `workspace_root_test` changed the
    working directory for Insta compatibility while the inner `rules_rust`
    sharding wrapper still expected its runfiles working directory. In
    practice, Bazel could report success without executing the Rust test
    cases in that shard. Once #18913 makes the wrapper run the Rust test
    binary directly and shard with libtest arguments, this stale unit test
    actually runs and exposes the missing `codex_self_exe` setup.
    
    ## What Changed
    
    - Moved `build_prompt_input_includes_context_and_user_message` out of
    `core/src/prompt_debug.rs`.
    - Added `core/tests/suite/prompt_debug_tests.rs` and registered it from
    `core/tests/suite/mod.rs`.
    - Builds the test config with `ConfigBuilder` and provides
    `codex_self_exe` using the current test executable, matching the
    runtime-path invariant required by prompt debug setup.
    - Preserves the existing assertions that the generated prompt input
    includes both the debug user message and project-specific user
    instructions.
    
    ## Verification
    
    - `cargo test -p codex-core --test all
    prompt_debug_tests::build_prompt_input_includes_context_and_user_message`
    - `bazel test //codex-rs/core:core-all-test
    --test_arg=prompt_debug_tests::build_prompt_input_includes_context_and_user_message
    --test_output=errors`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18916).
    * #18913
    * __->__ #18916
  • sandbox: remove dead seatbelt helper and update tests (#17859)
    ## Why
    
    `spawn_command_under_seatbelt()` in `codex-rs/core/src/seatbelt.rs` had
    fallen out of production use and was only referenced by test-only
    wrappers. That left us with sandbox tests that could stay green even if
    the actual seatbelt exec path regressed, because production shell
    execution now flows through `SandboxManager::transform()` and
    `ExecRequest::from_sandbox_exec_request()` instead of that helper.
    
    Removing the dead helper also exposed one downstream `codex-exec`
    integration test that still imported it, which broke `just clippy`.
    
    ## What Changed
    
    - Removed `codex-rs/core/src/seatbelt.rs` and stopped exporting
    `codex_core::seatbelt`.
    - Removed the redundant `codex-rs/core/tests/suite/seatbelt.rs` coverage
    that only exercised the dead helper.
    - Kept the `openpty` regression check, but moved it into
    `codex-rs/core/tests/suite/exec.rs` so it now runs through
    `process_exec_tool_call()`.
    - Fixed the seatbelt denial test in `codex-rs/core/tests/suite/exec.rs`
    to use `/usr/bin/touch`, so it actually exercises the sandbox instead of
    a nonexistent path.
    - Updated `codex-rs/exec/tests/suite/sandbox.rs` on macOS to build the
    sandboxed command through `build_exec_request()` and spawn the
    transformed command, instead of importing the removed helper.
    - Left the lower-level seatbelt policy coverage in
    `codex-rs/sandboxing/src/seatbelt_tests.rs`, where the policy generator
    is still covered directly.
    
    ## Verification
    
    - `cargo test -p codex-core suite::exec::`
    - `cargo test -p codex-exec`
    - `cargo clippy -p codex-exec --tests -- -D warnings`
  • Stabilize exec-server filesystem tests in CI (#17671)
    ## Summary\n- add an exec-server package-local test helper binary that
    can run exec-server and fs-helper flows\n- route exec-server filesystem
    tests through that helper instead of cross-crate codex helper
    binaries\n- stop relying on Bazel-only extra binary wiring for these
    tests\n\n## Testing\n- not run (per repo guidance for codex changes)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Run exec-server fs operations through sandbox helper (#17294)
    ## Summary
    - run exec-server filesystem RPCs requiring sandboxing through a
    `codex-fs` arg0 helper over stdin/stdout
    - keep direct local filesystem execution for `DangerFullAccess` and
    external sandbox policies
    - remove the standalone exec-server binary path in favor of top-level
    arg0 dispatch/runtime paths
    - add sandbox escape regression coverage for local and remote filesystem
    paths
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    - remote devbox: `cd codex-rs && bazel test --bes_backend=
    --bes_results_url= //codex-rs/exec-server:all` (6/6 passed)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: add Codex Apps sediment file remapping (#15197)
    ## Summary
    - bridge Codex Apps tools that declare `_meta["openai/fileParams"]`
    through the OpenAI file upload flow
    - mask those file params in model-visible tool schemas so the model
    provides absolute local file paths instead of raw file payload objects
    - rewrite those local file path arguments client-side into
    `ProvidedFilePayload`-shaped objects before the normal MCP tool call
    
    ## Details
    - applies to scalar and array file params declared in
    `openai/fileParams`
    - Codex uploads local files directly to the backend and uses the
    uploaded file metadata to build the MCP tool arguments locally
    - this PR is input-only
    
    ## Verification
    - `just fmt`
    - `cargo test -p codex-core mcp_tool_call -- --nocapture`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Make AGENTS.md discovery FS-aware (#15826)
    ## Summary
    - make AGENTS.md discovery and loading fully FS-aware and remove the
    non-FS discover helper
    - migrate remote-aware codex-core tests to use TestEnv workspace setup
    instead of syncing a local workspace copy
    - add AGENTS.md corner-case coverage, including directory fallbacks and
    remote-aware integration coverage
    
    ## Testing
    - cargo test -p codex-core project_doc -- --nocapture
    - cargo test -p codex-core hierarchical_agents -- --nocapture
    - cargo test -p codex-core agents_md -- --nocapture
    - cargo test -p codex-tui status -- --nocapture
    - cargo test -p codex-tui-app-server status -- --nocapture
    - just fix
    - just fmt
    - just bazel-lock-update
    - just bazel-lock-check
    - just argument-comment-lint
    - remote Linux executor tests in progress via scripts/test-remote-env.sh
  • [codex] add response proxy subagent header test (#16876)
    This adds end-to-end coverage for `responses-api-proxy` request dumps
    when Codex spawns a subagent and validates that the `x-codex-window-id`
    and `x-openai-subagent` are properly set.
  • [codex] add context-window lineage headers (#16758)
    This change adds client-owned context-window and parent thread id
    headers to all requests to responses api.
  • extract models manager and related ownership from core (#16508)
    ## Summary
    - split `models-manager` out of `core` and add `ModelsManagerConfig`
    plus `Config::to_models_manager_config()` so model metadata paths stop
    depending on `core::Config`
    - move login-owned/auth-owned code out of `core` into `codex-login`,
    move model provider config into `codex-model-provider-info`, move API
    bridge mapping into `codex-api`, move protocol-owned types/impls into
    `codex-protocol`, and move response debug helpers into a dedicated
    `response-debug-context` crate
    - move feedback tag emission into `codex-feedback`, relocate tests to
    the crates that now own the code, and keep broad temporary re-exports so
    this PR avoids a giant import-only rewrite
    
    ## Major moves and decisions
    - created `codex-models-manager` as the owner for model
    cache/catalog/config/model info logic, including the new
    `ModelsManagerConfig` struct
    - created `codex-model-provider-info` as the owner for provider config
    parsing/defaults and kept temporary `codex-login`/`codex-core`
    re-exports for old import paths
    - moved `api_bridge` error mapping + `CoreAuthProvider` into
    `codex-api`, while `codex-login::api_bridge` temporarily re-exports
    those symbols and keeps the `auth_provider_from_auth` wrapper
    - moved `auth_env_telemetry` and `provider_auth` ownership to
    `codex-login`
    - moved `CodexErr` ownership to `codex-protocol::error`, plus
    `StreamOutput`, `bytes_to_string_smart`, and network policy helpers to
    protocol-owned modules
    - created `codex-response-debug-context` for
    `extract_response_debug_context`, `telemetry_transport_error_message`,
    and related response-debug plumbing instead of leaving that behavior in
    `core`
    - moved `FeedbackRequestTags`, `emit_feedback_request_tags`, and
    `emit_feedback_request_tags_with_auth_env` to `codex-feedback`
    - deferred removal of temporary re-exports and the mechanical import
    rewrites to a stacked follow-up PR so this PR stays reviewable
    
    ## Test moves
    - moved auth refresh coverage from `core/tests/suite/auth_refresh.rs` to
    `login/tests/suite/auth_refresh.rs`
    - moved text encoding coverage from
    `core/tests/suite/text_encoding_fix.rs` to
    `protocol/src/exec_output_tests.rs`
    - moved model info override coverage from
    `core/tests/suite/model_info_overrides.rs` to
    `models-manager/src/model_info_overrides_tests.rs`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: fix old system bubblewrap compatibility without falling back to vendored bwrap (#15693)
    Fixes #15283.
    
    ## Summary
    Older system bubblewrap builds reject `--argv0`, which makes our Linux
    sandbox fail before the helper can re-exec. This PR keeps using system
    `/usr/bin/bwrap` whenever it exists and only falls back to vendored
    bwrap when the system binary is missing. That matters on stricter
    AppArmor hosts, where the distro bwrap package also provides the policy
    setup needed for user namespaces.
    
    For old system bwrap, we avoid `--argv0` instead of switching binaries:
    - pass the sandbox helper a full-path `argv0`,
    - keep the existing `current_exe() + --argv0` path when the selected
    launcher supports it,
    - otherwise omit `--argv0` and re-exec through the helper's own
    `argv[0]` path, whose basename still dispatches as
    `codex-linux-sandbox`.
    
    Also updates the launcher/warning tests and docs so they match the new
    behavior: present-but-old system bwrap uses the compatibility path, and
    only absent system bwrap falls back to vendored.
    
    ### Validation
    
    1. Install Ubuntu 20.04 in a VM
    2. Compile codex and run without bubblewrap installed - see a warning
    about falling back to the vendored bwrap
    3. Install bwrap and verify version is 0.4.0 without `argv0` support
    4. run codex and use apply_patch tool without errors
    
    <img width="802" height="631" alt="Screenshot 2026-03-25 at 11 48 36 PM"
    src="https://github.com/user-attachments/assets/77248a29-aa38-4d7c-9833-496ec6a458b8"
    />
    <img width="807" height="634" alt="Screenshot 2026-03-25 at 11 47 32 PM"
    src="https://github.com/user-attachments/assets/5af8b850-a466-489b-95a6-455b76b5050f"
    />
    <img width="812" height="635" alt="Screenshot 2026-03-25 at 11 45 45 PM"
    src="https://github.com/user-attachments/assets/438074f0-8435-4274-a667-332efdd5cb57"
    />
    <img width="801" height="623" alt="Screenshot 2026-03-25 at 11 43 56 PM"
    src="https://github.com/user-attachments/assets/0dc8d3f5-e8cf-4218-b4b4-a4f7d9bf02e3"
    />
    
    ---------
    
    Co-authored-by: Michael Bolin <mbolin@openai.com>
  • [apps][tool_suggest] Remove tool_suggest's dependency on tool search. (#14856)
    - [x] Remove tool_suggest's dependency on tool search.
  • chore: remove grep_files handler (#15775)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add remote env CI matrix and integration test (#14869)
    `CODEX_TEST_REMOTE_ENV` will make `test_codex` start the executor
    "remotely" (inside a docker container) turning any integration test into
    remote test.
  • [hooks] stop continuation & stop_hook_active mechanics (#14532)
    Stop hooks now receive `stop_hook_active` and enable stop hooks to loop
    forever if they'd like to. In the initial hooks PR, we implemented a
    simpler mechanic that the stop-blocking could only happen once in a row
    
    - support stop hook adding a continuation prompt to add a further task
    - if multiple stop-blocks happen that have continuation prompts, they
    are concatenated
    
    example run:
    ```
    › hey :)
    
    
    • Running SessionStart hook: lighting the observatory
    
    SessionStart hook (completed)
      warning: Hi, I'm a session start hook for wizard-tower (startup).
      hook context: Startup context: A wimboltine stonpet is an exotic cuisine from hyperspace
    
    • Aloha :) Happy to jam with you. What are we building today?
    
    • Running Stop hook: updating the guards
    
    Stop hook (blocked)
      warning: Wizard Tower Stop hook continuing conversation
      feedback: cook the stonpet
    
    • Aloha, here’s the hyperspace move for cooking a wimboltine stonpet:
    
      1. Sear the stonpet in a hot pan with moon-oil until the edges shimmer.
      2. Add star-lime, black salt, and a little fermented nebula paste.
      3. Lower the heat and let it braise for 8 cosmic minutes with a splash of comet broth.
      4. Finish with sky herbs and serve over warm asteroid rice.
    
      The vibe: crispy outside, tender center, deep interdimensional savor.
    
    • Running Stop hook: updating the guards
    
    Stop hook (completed)
      warning: Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop.
    ```
    
    .codex/config.toml
    ```
    [features]
    codex_hooks = true
    ```
    
    .codex/hooks.json
    ```
    {
      "hooks": {
        "SessionStart": [
          {
            "matcher": "startup|resume",
            "hooks": [
              {
                "type": "command",
                "command": "/usr/bin/python3 .codex/hooks/session_start_demo.py",
                "timeoutSec": 10,
                "statusMessage": "lighting the observatory"
              }
            ]
          }
        ],
        "Stop": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "/usr/bin/python3 .codex/hooks/stop_demo_block.py",
                "timeoutSec": 10,
                "statusMessage": "updating the guards"
              }
            ]
          }
        ]
      }
    }
    ```
    
    .codex/hooks/session_start_demo.py
    ```
    #!/usr/bin/env python3
    
    import json
    import sys
    from pathlib import Path
    
    
    def main() -> int:
        payload = json.load(sys.stdin)
        cwd = Path(payload.get("cwd", ".")).name or "wizard-tower"
        source = payload.get("source", "startup")
        source_label = "resume" if source == "resume" else "startup"
        source_prefix = (
            "Resume context:"
            if source == "resume"
            else "Startup context:"
        )
    
        output = {
            "systemMessage": (
                f"Hi, I'm a session start hook for {cwd} ({source_label})."
            ),
            "hookSpecificOutput": {
                "hookEventName": "SessionStart",
                "additionalContext": (
                    f"{source_prefix} A wimboltine stonpet is an exotic cuisine from hyperspace"
                ),
            },
        }
        print(json.dumps(output))
        return 0
    
    
    if __name__ == "__main__":
        raise SystemExit(main())
    ```
    
    .codex/hooks/stop_demo_block.py
    ```
    #!/usr/bin/env python3
    
    import json
    import sys
    
    
    def main() -> int:
        payload = json.load(sys.stdin)
        stop_hook_active = payload.get("stop_hook_active", False)
        last_assistant_message = payload.get("last_assistant_message") or ""
        char_count = len(last_assistant_message.strip())
    
        if stop_hook_active:
            system_message = (
                "Wizard Tower Stop hook saw a second pass and stayed calm to avoid a loop."
            )
            print(json.dumps({"systemMessage": system_message}))
        else:
            system_message = (
                f"Wizard Tower Stop hook continuing conversation"
            )
            print(json.dumps({"systemMessage": system_message, "decision": "block", "reason": "cook the stonpet"}))
    
        return 0
    
    
    if __name__ == "__main__":
        raise SystemExit(main())
    ```
  • spawn prompt (#14362)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • Add code_mode experimental feature (#13418)
    A much narrower and more isolated (no node features) version of js_repl
  • feat(core) Persist request_permission data across turns (#14009)
    ## Summary
    request_permissions flows should support persisting results for the
    session.
    
    Open Question: Still deciding if we need within-turn approvals - this
    adds complexity but I could see it being useful
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: load from plugins (#12864)
    Support loading plugins.
    
    Plugins can now be enabled via [plugins.<name>] in config.toml. They are
    loaded as first-class entities through PluginsManager, and their default
    skills/ and .mcp.json contributions are integrated into the existing
    skills and MCP flows.
  • feat: include sandbox config with escalation request (#12839)
    ## Why
    
    Before this change, an escalation approval could say that a command
    should be rerun, but it could not carry the sandbox configuration that
    should still apply when the escalated command is actually spawned.
    
    That left an unsafe gap in the `zsh-fork` skill path: skill scripts
    under `scripts/` that did not declare permissions could be escalated
    without a sandbox, and scripts that did declare permissions could lose
    their bounded sandbox on rerun or cached session approval.
    
    This PR extends the escalation protocol so approvals can optionally
    carry sandbox configuration all the way through execution. That lets the
    shell runtime preserve the intended sandbox instead of silently widening
    access.
    
    We likely want a single permissions type for this codepath eventually,
    probably centered on `Permissions`. For now, the protocol needs to
    represent both the existing `PermissionProfile` form and the fuller
    `Permissions` form, so this introduces a temporary disjoint union,
    `EscalationPermissions`, to carry either one.
    
    Further, this means that today, a skill either:
    
    - does not declare any permissions, in which case it is run using the
    default sandbox for the turn
    - specifies permissions, in which case the skill is run using that exact
    sandbox, which might be more restrictive than the default sandbox for
    the turn
    
    We will likely change the skill's permissions to be additive to the
    existing permissions for the turn.
    
    ## What Changed
    
    - Added `EscalationPermissions` to `codex-protocol` so escalation
    requests can carry either a `PermissionProfile` or a full `Permissions`
    payload.
    - Added an explicit `EscalationExecution` mode to the shell escalation
    protocol so reruns distinguish between `Unsandboxed`, `TurnDefault`, and
    `Permissions(...)` instead of overloading `None`.
    - Updated `zsh-fork` shell reruns to resolve `TurnDefault` at execution
    time, which keeps ordinary `UseDefault` commands on the turn sandbox and
    preserves turn-level macOS seatbelt profile extensions.
    - Updated the `zsh-fork` skill path so a skill with no declared
    permissions inherits the conversation's effective sandbox instead of
    escalating unsandboxed.
    - Updated the `zsh-fork` skill path so a skill with declared permissions
    reruns with exactly those permissions, including when a cached session
    approval is reused.
    
    ## Testing
    
    - Added unit coverage in
    `core/src/tools/runtimes/shell/unix_escalation.rs` for the explicit
    `UseDefault` / `RequireEscalated` / `WithAdditionalPermissions`
    execution mapping.
    - Added unit coverage in
    `core/src/tools/runtimes/shell/unix_escalation.rs` for macOS seatbelt
    extension preservation in both the `TurnDefault` and
    explicit-permissions rerun paths.
    - Added integration coverage in `core/tests/suite/skill_approval.rs` for
    permissionless skills inheriting the turn sandbox and explicit skill
    permissions remaining bounded across cached approval reuse.