Commit Graph

1353 Commits

  • core: enable remote compaction v2 by default (#27573)
    ## Why
    
    Remote compaction v2 is ready to become the default for providers that
    already support remote compaction. Leaving it behind an
    under-development opt-in keeps eligible sessions on the legacy
    remote-compaction path.
    
    This does not broaden provider eligibility: OpenAI and Azure move to v2,
    while Bedrock and OSS providers retain their existing local-compaction
    behavior.
    
    ## What changed
    
    - Mark `remote_compaction_v2` stable and enable it by default.
    - Make tests that intentionally cover legacy remote compaction
    explicitly disable v2.
    - Update parity coverage so v2 exercises the production default and only
    legacy mode opts out.
    
    ## Verification
    
    - `just test -p codex-core
    auto_compact_runs_after_resume_when_token_usage_is_over_limit
    auto_compact_counts_encrypted_reasoning_before_last_user
    auto_compact_runs_when_reasoning_header_clears_between_turns
    responses_lite_compact_request_uses_lite_transport_contract`
  • [codex] Add context remaining tool (#27518)
    ## Why
    
    The token budget feature can inject remaining-context notices into
    model-visible context, but the model does not have a direct way to ask
    for that same remaining-token fragment on demand.
    
    This PR adds a small model tool for the token budget feature so the
    model can request the current remaining context window message without
    duplicating the fragment format.
    
    ## What changed
    
    - Adds a `get_context_remaining` direct-model tool behind
    `Feature::TokenBudget`.
    - Renders the tool output through `TokenBudgetRemainingContext`,
    matching the existing budget message shape.
    - Registers the tool alongside `new_context` in the token budget tool
    set.
    - Adds integration coverage that verifies the tool is exposed and
    returns the same `<token_budget>` remaining fragment already present in
    context.
    
    ## Validation
    
    - `just test -p codex-core token_budget`
  • [codex] Compact when comp_hash changes (#27520)
    ## Summary
    - snapshot `comp_hash` into `TurnContext` when the turn is created and
    use that snapshot as the downstream source of truth
    - persist the turn hash in rollout context and recover it into
    previous-turn settings during resume and fork replay
    - compact existing history with the previous model only when both
    adjacent turns provide hashes and the values differ
    - record `comp_hash_changed` as the compaction reason
    - cover ordinary transitions, resume, and missing-hash compatibility
    with end-to-end tests
    
    ## Why
    History produced under one compaction-compatible model configuration may
    not be safe to carry directly into another. Compacting at the turn
    boundary converts that history before context updates and the new user
    message are added. Persisting the turn snapshot in `TurnContextItem`
    makes the same protection work after resuming a rollout.
    
    A missing hash is not treated as evidence of incompatibility. `None →
    Some`, `Some → None`, and `None → None` do not trigger compaction; only
    `Some(previous) → Some(current)` with unequal values does.
    
    ## Stack
    - depends on #27532
    - #27532 is based directly on `main`
    
    ## Testing
    - `just test -p codex-core pre_sampling_compact_` — 6 passed
    - `just test -p codex-core
    turn_context_item_uses_turn_context_comp_hash_snapshot` — passed
    - `just fix -p codex-core -p codex-protocol -p codex-analytics -p
    codex-models-manager`
  • core: strip image detail from Responses Lite requests (#27246)
    ## Summary
    
    - Strip image `detail` fields from every Responses Lite request.
    - Apply stripping to message images and function/custom tool-output
    images.
    - Transform only the formatted request copy without mutating stored
    history.
    - Preserve image URLs byte-for-byte, including HTTP(S) URLs, without
    downloading, validating, or resizing them.
    - Preserve all image `detail` fields for non-Responses-Lite models.
    
    ## Motivation
    
    Responses Lite does not support image `detail` tags, so Codex must omit
    them whenever `model_info.use_responses_lite` is enabled. This transport
    requirement is independent of the `resize_all_images` feature.
    
    Stored history retains the original detail values. This keeps
    request-specific formatting isolated from conversation state and
    preserves the information for local image preparation and
    non-Responses-Lite requests.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/27245
    -  `2` https://github.com/openai/codex/pull/27247
    - 👉 `3` https://github.com/openai/codex/pull/27246
    -  `4` https://github.com/openai/codex/pull/27266
  • [codex] Add comp_hash to model metadata (#27532)
    ## Summary
    - add optional `comp_hash` metadata to `ModelInfo`
    - update `ModelInfo` fixtures for the shared schema change
    - keep older model responses compatible by defaulting the field to
    `None`
    
    ## Why
    The models endpoint needs an opaque identifier for compaction-compatible
    model configurations. This PR only exposes that value in model metadata;
    it does not add it to turn context or change runtime behavior.
    
    Follow-up #27520 carries the value through turn context and rollouts,
    then uses it to trigger compaction.
    
    ## Stack
    - based directly on `main`
    - replaces #27519, which was accidentally merged into the wrong base
    branch
    - functionality follow-up: #27520
    
    ## Testing
    - `just test -p codex-protocol
    model_info_defaults_availability_nux_to_none_when_omitted`
    - `just fix -p codex-core -p codex-protocol -p codex-analytics -p
    codex-models-manager`
  • [codex] Add new context window tool (#27488)
    ## Why
    
    The token budget feature tells the model how much room remains in the
    current context window. When the model decides the current window is no
    longer useful, it needs a way to ask Codex to start over with a fresh
    context window without spending tokens on a compaction summary.
    
    This PR adds that model-requestable escape hatch on top of #27438.
    
    ## What changed
    
    - Added a direct-model-only `new_context` tool behind
    `Feature::TokenBudget`.
    - Stores the tool request on `AutoCompactWindow` and consumes it after
    sampling so the next follow-up request in the same turn starts in the
    new window.
    - Starts the new window as a no-summary compaction checkpoint that
    contains only fresh initial context, not preserved conversation history.
    - Keeps the new window aligned with token-budget startup context,
    including the `Current context window Z` message.
    - Added integration coverage and a snapshot showing the same-turn
    `new_context` flow into a fresh full-context follow-up request.
    
    ## Validation
    
    - `just test -p codex-core token_budget`
  • [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`
  • core: resize all history images behind a feature flag (#27247)
    ## Summary
    
    Adds complete client-side image preparation behind the default-off
    `resize_all_images` feature flag.
    
    When enabled, local image producers defer decoding and resizing. Images
    are prepared centrally before insertion into conversation history,
    covering user input, `view_image`, and structured tool-output images.
    
    ## Behavior
    
    - Processes base64 `data:` images in messages and function/custom tool
    outputs.
    - Leaves non-data URLs, including HTTP(S) URLs, unchanged.
    - Applies image-detail budgets:
      - `high` and omitted: 2048px maximum dimension and 2.5K 32px patches.
      - `original`: 6000px maximum dimension and 10K 32px patches.
      - `auto`: uses the same 2048px / 2.5K-patch budget as high.
      - `low`: unsupported and replaced with an actionable placeholder.
    - Preserves original image bytes when no resize or format conversion is
    needed.
    - Enforces the shared 1 GiB encoded and decoded data-URL sanity limits.
    - Replaces only an image that fails preparation, preserving sibling
    content and tool-output metadata.
    - Uses bounded placeholders distinguishing generic processing failures,
    oversized images, and unsupported `low` detail.
    - Prepares resumed and forked history before installing it as live
    history without modifying persisted rollouts.
    
    ## Flag-Off Behavior
    
    When `resize_all_images` is disabled:
    
    - Existing local user-input and `view_image` processing remains
    unchanged.
    - Existing decoding and error behavior remains unchanged.
    - Arbitrary tool-output images are not processed.
    - HTTP(S) image URLs continue to be forwarded unchanged.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/27245
    - 👉 `2` https://github.com/openai/codex/pull/27247
    -  `3` https://github.com/openai/codex/pull/27246
    -  `4` https://github.com/openai/codex/pull/27266
  • Guard core test subprocess cleanup (#27343)
    ## Why
    
    Local integration-heavy `codex-core` CLI tests can time out or be
    interrupted after spawning `codex exec`. Stopping only the direct child
    is not enough: `codex exec` can leave grandchildren behind, including
    `python3`/`python3.12` processes that get reparented to PID 1 and keep
    running after the test is gone.
    
    This PR fixes that failure mode directly for the affected CLI
    integration tests, without changing production code or reducing local
    test concurrency.
    
    ## What
    
    - Run the `cli_stream` `codex exec` subprocesses through a small private
    wrapper in `core/tests/suite/cli_stream.rs`.
    - Spawn those subprocesses in their own process group before execution.
    - Keep `.output()`-style stdout/stderr capture and the existing
    30-second timeout behavior.
    - Own each spawned process with a drop guard that kills the whole
    process group on success, timeout, panic, or other early return.
    
    The switch from `assert_cmd::Command` to `std::process::Command` is only
    for these subprocess launches; `assert_cmd` does not expose a pre-spawn
    hook for setting the process group.
    
    ## Verification
    
    - `just test -p codex-core --test all responses_mode_stream_cli`
    
    This is limited to core integration tests; it does not change production
    `src` code paths.
  • [codex] Store compact window id in rollout (#27264)
    ## Why
    
    Compaction window identity is part of session history, not model-client
    transport state. Persisting it with the compacted rollout item lets
    resumed threads continue from the reconstructed window without keeping
    mutable window state on `ModelClient`.
    
    ## What changed
    
    - Added `window_id` to `CompactedItem` and stamp it when
    `replace_compacted_history` installs compacted history.
    - Moved auto-compact window id ownership into `AutoCompactWindow` /
    `SessionState`; `ModelClient` now receives the request window id from
    callers instead of storing it.
    - Returned `window_id` from rollout reconstruction for resume.
    Reconstruction uses the newest surviving compacted item's stored
    `window_id` when present, and falls back to the legacy compacted-item
    count when it is absent.
    - Kept fork startup at the fresh default window id and updated direct
    model-client tests to pass explicit test window ids.
    
    ## Validation
    
    - `cargo check -p codex-core --tests`
  • Use latest-wins MCP manager replacement (#27259)
    ## Summary
    
    We originally addressed startup prewarming holding the read side of
    `RwLock<McpConnectionManager>` by snapshotting tool-list state. Review
    feedback identified the broader ownership problem: the outer
    synchronization should only publish or retrieve the current manager,
    while MCP operations rely on the manager's internal synchronization. A
    follow-up preserved operation retirement with a separate gate, but
    further review questioned whether that synchronization was actually
    required and whether we could support latest-wins replacement instead.
    
    This PR now stores the current MCP manager in `ArcSwap`. Each operation
    uses `load_full()` to obtain an owned `Arc<McpConnectionManager>`, then
    performs MCP I/O without retaining the publication mechanism. Refresh
    cancels obsolete startup work, constructs a replacement, and atomically
    publishes it. New operations see the latest manager, while operations
    that already loaded the previous manager retain a valid handle. Refresh
    happens at a turn boundary, so there should be no active user tool calls
    to drain.
    
    Git history supports dropping the outer `RwLock`. It was introduced in
    `03ffe4d595` on November 17, 2025 for non-blocking MCP startup: the
    session published an empty manager, startup initialized that same object
    while holding the write lock, and readers waited for initialization.
    `7cd2e84026` on February 19, 2026 removed that two-phase initialization
    in favor of constructing a fresh manager and swapping it in, explicitly
    noting that `Option` or `OnceCell` could replace the placeholder design.
    Hot reload later reused the existing lock to publish a replacement, but
    I found no indication that the lock was introduced to guarantee
    in-flight tool calls finish before refresh or shutdown.
    
    Terminal shutdown remains separate from refresh: it aborts startup
    prewarming and active tasks before shutting down the current manager, so
    tool calls may be interrupted and no model WebSocket work continues
    after shutdown. Focused regression coverage exercises pending tool-list
    cancellation, deferred refresh, and startup-prewarm shutdown.
  • Add per-session realtime model and version overrides (#24999)
    ## Why
    
    Clients need to select a realtime session configuration for an
    individual start without rewriting persisted configuration or restarting
    the app-server process.
    
    ## What Changed
    
    - Add optional `model` and `version` fields to `thread/realtime/start`
    - Forward those optional values through the realtime start operation and
    apply them only for that session
    - Preserve existing configured/default behavior when the new fields are
    omitted
    - Update generated protocol schema and app-server documentation
    
    ## Validation
    
    - Added/updated protocol serialization coverage for the new optional
    request fields
    - Added focused core coverage for a session override taking precedence
    over configured realtime selection
    - Added focused app-server coverage that a request override reaches the
    realtime WebSocket handshake
  • Stop mirroring Codex user input into realtime (#27116)
    ## Why
    
    The realtime frontend model and the backing Codex thread should present
    one coherent assistant. Raw typed messages, steers, and worker reports
    belong to the orchestrator; the frontend model should receive the
    orchestrator's user-facing result rather than a second copy of those
    inputs.
    
    Today normal `turn/start` input is automatically inserted into the
    realtime conversation, while `turn/steer` is not. Besides creating
    inconsistent context, this can make the frontend model react
    independently before Codex has produced the response it should speak.
    
    ## What changed
    
    - Remove automatic accepted-user-input mirroring into realtime
    - Remove the mirror-only echo-suppression flag and dead V2 prefix helper
    - Preserve explicit app-to-realtime text injection and FEM-to-Codex
    delegation
    - Replace the positive mirror tests and obsolete snapshots with a
    negative routing regression test
    
    ## Test plan
    
    - `cargo test -p codex-core
    conversation_user_text_turn_is_not_sent_to_realtime`
    - `cargo test -p codex-core
    conversation_startup_context_is_truncated_and_sent_once_per_start`
    - `cargo test -p codex-core inbound_handoff_request_starts_turn`
  • [codex] Handle Ctrl-C for non-TTY unified exec (#26734)
    ## Why
    
    A long-running unified exec process started with `tty: false` could not
    be interrupted via `write_stdin`: ordinary non-TTY stdin writes are
    rejected once stdin is closed, but an exact U+0003 payload should still
    map to a process interrupt. The interrupt should flow through the same
    process lifecycle path as a real signal so Codex preserves
    process-reported output and exit metadata instead of fabricating a
    Ctrl-C exit code or tearing down the session early.
    
    ## What Changed
    
    - Add `process/signal` to exec-server with `ProcessSignal::Interrupt`
    and an empty response.
    - Add a non-consuming `ProcessHandle::signal` path for spawned
    processes; on Unix it sends SIGINT to the process group and leaves
    terminate/hard-kill unchanged.
    - Route non-TTY U+0003 `write_stdin` through `process.signal(...)`
    instead of `terminate`, then let the normal post-write collection path
    drain output and observe exit.
    - Add exec-server coverage where a shell `trap INT` handler prints the
    signal and exits with its own code.
    - Add unified exec coverage where a `tty: false` process traps SIGINT,
    emits output, and exits with its own code.
    
    ## Validation
    
    - `just test -p codex-exec-server
    exec_process_signal_interrupts_process`
    - `just test -p codex-exec-server`
    - `just test -p codex-core
    write_stdin_ctrl_c_interrupts_non_tty_session`
  • [codex] Characterize global instruction lifecycle (#26830)
    ## Why
    
    Global instruction behavior spans thread creation, resume, forks,
    subagents, and compaction. Characterization coverage is needed before
    changing those semantics so preserved history can be distinguished from
    newly loaded configuration.
    
    ## What changed
    
    - Extends the existing `agents_md` suite with fresh-thread, warning,
    resume, fork, and subagent lifecycle coverage.
    - Extends the existing `compact` suite with manual, mid-turn, and
    remote-v2 compaction coverage.
    - Asserts rendered instruction fragments, reported source paths, and
    structured request history before and after instruction-file mutations.
  • Load selected executor skills through extensions (#27184)
    ## Why
    
    CCA is moving toward a split runtime where the orchestrator may not have
    a filesystem, while executors can expose preinstalled plugins and
    skills. A thread therefore needs to select capabilities without asking
    app-server or core to interpret executor-owned paths through the
    orchestrator's filesystem.
    
    The longer-term model is broader than executor skills:
    
    - A plugin is a bundle of skills, MCP servers, connectors/apps, and
    hooks.
    - A plugin root can be local, executor-owned, or hosted by a backend.
    - Components inside one plugin can use different access and execution
    mechanisms. A skill may be read from a filesystem or through backend
    tools; an HTTP MCP server can run without an executor; a stdio MCP
    server or hook needs an execution environment.
    - Core should carry generic extension initialization data. The extension
    that owns a component should discover it, expose it to the model, and
    invoke it through the appropriate runtime.
    
    This PR establishes that architecture through one complete vertical:
    selecting a root on an executor, discovering the skills beneath it,
    exposing those skills to the model, and reading an explicitly invoked
    `SKILL.md` through the same executor.
    
    ## Contract
    
    `thread/start` gains an experimental `selectedCapabilityRoots` field:
    
    ```json
    {
      "selectedCapabilityRoots": [
        {
          "id": "deploy-plugin@1",
          "location": {
            "type": "environment",
            "environmentId": "workspace",
            "path": "/opt/codex/plugins/deploy"
          }
        }
      ]
    }
    ```
    
    The root is intentionally not classified as a "plugin" or "skill" in the
    API. It can point at a standalone skill, a directory containing several
    skills, or a plugin containing skills and other components. This PR only
    teaches the skills extension how to consume it; later extensions can
    resolve MCP, connector, and hook components from the same selection.
    
    The platform-supplied `id` is stable selection identity. The location
    says which runtime owns the root and gives that runtime an opaque path.
    App-server does not inspect or canonicalize the path.
    
    ## What changed
    
    ### Generic thread extension initialization
    
    App-server converts selected roots into `ExtensionDataInit`. Core
    carries that generic initialization value until the final thread ID is
    known, then creates thread-scoped `ExtensionData` before lifecycle
    contributors run.
    
    This keeps `Session` and core independent of the capability-selection
    contract. The initialization value is consumed during construction; it
    is not retained as another long-lived `Session` field.
    
    ### Executor-backed skills
    
    The skills extension now owns an `ExecutorSkillProvider` that:
    
    - resolves the selected environment through `EnvironmentManager`
    - discovers, canonicalizes, and reads skills through that environment's
    `ExecutorFileSystem`
    - contributes the bounded selected-skill catalog as stable developer
    context
    - reads an explicitly invoked skill body through the authority that
    listed it
    - warns when an environment or root is unavailable
    - never falls back to the orchestrator filesystem for an executor-owned
    root
    
    Skill catalog and instruction fragments have hard byte bounds, which
    also bound them below the 10K-token per-item context limit. If a
    selected executor skill has the same name as a legacy local skill, the
    executor selection owns that invocation and the local body is not
    injected a second time.
    
    Existing local and bundled skill loading remains in place. Omitting
    `selectedCapabilityRoots` therefore preserves current local-only
    behavior.
    
    ## Current semantics
    
    - Only environment-owned locations are represented in this first
    contract.
    - Roots are resolved by the destination extension, not by app-server or
    core.
    - An unavailable executor or invalid root produces a warning and no
    capabilities from that root; it does not trigger a local-filesystem
    fallback.
    - Selection applies to a newly started active thread.
    - MCP servers, connectors, and hooks beneath a selected plugin root are
    not activated yet.
    - Selection is not yet persisted or inherited across resume, fork, or
    subagent creation. Existing local capabilities continue to behave as
    they do today in those flows.
    
    ## Planned vertical follow-ups
    
    1. **Hosted HTTP MCP:** add an extension-backed HTTP MCP source that
    works without an executor, then replace the special-purpose MCP plugins
    loader with that implementation.
    2. **Executor MCP:** register and execute stdio MCP servers through the
    environment that owns the selected plugin root.
    3. **Backend skills:** add a hosted skill source whose catalog and
    bodies are accessed through extension tools rather than a filesystem.
    4. **Connectors and hooks:** activate those components through their
    owning extensions, using the same selected-root boundary and
    component-specific runtime.
    5. **Durable selection:** define the desired-selection lifecycle,
    persist it, and make resume, fork, and subagent inheritance explicit
    rather than accidental.
    6. **Local convergence:** incrementally route existing local plugin,
    skill, and MCP loading through the same extension model while preserving
    current local behavior.
    
    Each follow-up remains reviewable as an end-to-end capability. The
    platform selects roots, generic thread extension data carries the
    selection, and the owning extension resolves and operates its component.
    
    ## Verification
    
    Coverage added for:
    
    - app-server end-to-end discovery and explicit invocation of a skill
    inside an executor-selected plugin root
    - exclusive invocation when a selected executor skill collides with a
    local skill name
    - executor filesystem authority for discovery, canonicalization, and
    reads
    - thread extension initialization before lifecycle contributors run
    - stable executor catalog context, explicit invocation, context
    rebuilding, hidden skills, and preserved host/remote catalog behavior
    
    Targeted protocol, core-skills, skills-extension, core lifecycle, and
    app-server executor-skill tests were run during development.
  • Pair thread environment settings (#26687)
    ## Why
    
    Thread cwd and environment selections are a single logical setting in
    core: updating one without the other can silently desynchronize the
    next-turn execution context. This change makes that relationship
    explicit in the internal thread settings flow while preserving the
    existing app-server public API shape.
    
    ## What changed
    
    - Moved the cwd/environment pair through internal
    `ThreadSettingsOverrides.environment_settings` instead of a top-level
    internal `cwd` field.
    - Kept `thread/settings/update` public params unchanged, with app-server
    translating top-level `cwd` into the paired internal settings shape.
    - Moved `Op::UserInput` environment overrides into thread settings so
    user turns and settings updates use the same core path.
    - Updated core, app-server, MCP, memories, sample, and test callsites to
    construct the paired settings shape.
    
    ## Verification
    
    - `git diff --check`
    - Local test run starting after PR creation.
  • Add HTTP window ID to Responses client metadata (#26923)
    ## Summary
    
    - Keep the existing `x-codex-window-id` HTTP header unchanged.
    - Also send the same window ID in Responses `client_metadata`, allowing
    supported backend paths to surface it as
    `x-client-meta-x-codex-window-id`.
    - Cover normal HTTP Responses and remote compaction v2 requests without
    changing window generation or compaction behavior.
    
    ## Why
    
    In the `2026-06-06T23` production hour, all 28,729 HTTP compaction
    requests had `window_id` in `x-codex-turn-metadata`, but only 73
    retained the direct `x-codex-window-id` header. The request-body
    `client_metadata` path is already used for installation ID and is
    preserved through supported Responses API paths.
    
    This is additive metadata only. It does not change the direct header,
    request count, model input, compaction routing, window generation, or
    user response behavior.
    
    Legacy `/v1/responses/compact` is intentionally unchanged. Its current
    server-side `CompressBody` schema does not accept `client_metadata` and
    rejects unknown fields, so supporting that path requires a backend
    schema change before the Codex client can safely send this field.
    
    ## Validation
    
    - Current head: `219baef3c`, rebased onto `origin/main` at `26d932983`.
    - The post-rebase diff remains limited to the original five files (`22`
    insertions, `6` deletions); the legacy experiment remains fully
    reverted.
    - `just test -p codex-core
    responses_stream_includes_subagent_header_on_review`: passed; validates
    normal HTTP Responses metadata.
    - `just test -p codex-core
    remote_compact_v2_reuses_compaction_trigger_for_followups`: passed;
    validates remote compaction v2.
    - `just test -p codex-core
    remote_manual_compact_chatgpt_auth_reuses_service_tier_and_prompt_cache_key`:
    passed; validates that legacy compact keeps its accepted payload shape.
    - `just test -p codex-core
    remote_manual_compact_api_auth_omits_service_tier_and_reuses_prompt_cache_key`:
    passed; validates the legacy API-key payload as well.
    - `just fmt`: passed; an unrelated root `justfile` rewrite produced by
    the formatter was discarded.
    - `git diff --check origin/main...HEAD`: passed.
    
    The focused server pytest could not start in the local monorepo
    environment because test setup is missing the `dotenv` module. Server
    source and tests explicitly show that `CompressBody` omits
    `client_metadata` and `/v1/responses/compact` returns HTTP 400 for
    unknown body fields.
  • [codex] Exclude external tool output from memories (#26821)
    ## Summary
    
    - add contains_external_context() to tool output so other tools can be
    opted out of influencing memory when disable_on_external_context=true
    - Classify standalone web-search output as external context (to match
    behavior as hosted web search)
    - Verify with integration test
  • 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`
  • [codex] Enable standalone web search in code mode (#26719)
    ## What
    
    - Consume plaintext `output` from standalone search while retaining
    optional `encrypted_output` parsing.
    - Expose `web.run` to code mode and return search output to nested
    JavaScript calls.
    - Cover direct and code-mode standalone search paths with integration
    tests.
    
    ## Why
    
    `/v1/alpha/search` now returns plaintext output, which code mode needs
    to consume standalone search results.
    
    ## Test plan
    
    - `just test -p codex-api`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-core code_mode_can_call_standalone_web_search`
    - `just test -p codex-app-server
    standalone_web_search_round_trips_output`
  • 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] Send Responses Lite transport header (#26542)
    ## Summary
    
    - send `X-OpenAI-Internal-Codex-Responses-Lite: true` on HTTP Responses
    requests and WebSocket upgrade requests when model metadata enables
    Responses Lite
    - use client metadata when sending it over the websocket
    
    This PR is stacked on #26490.
    
    ## Why
    
    The Responses Lite marker is request-scoped for HTTP but
    connection-scoped for Responses-over-WebSocket because it is carried on
    the upgrade request. Reusing a cached socket opened for the opposite
    mode would therefore send the wrong transport contract.
    
    ## Validation
    
    - `just test -p codex-core responses_lite`
    - `just test -p codex-core
    responses_websocket_reconnects_when_responses_lite_mode_changes`
    - `just fix -p codex-core`
    - `just fmt`
  • [codex-rs] support v2 personal access tokens (#25731)
    ## Summary
    
    - add v2 personal access token support for `codex login
    --with-access-token` and `CODEX_ACCESS_TOKEN`
    - classify opaque `at-` tokens separately from legacy Agent Identity
    JWTs
    - hydrate required ChatGPT account metadata through AuthAPI
    `/v1/user-auth-credential/whoami`
    - use PATs directly as bearer tokens while preserving existing ChatGPT
    account surfaces
    - expose PAT-backed auth as the explicit `personalAccessToken`
    app-server auth mode
    
    ## Implementation
    
    PAT auth is intentionally small and stateless. Loading a PAT performs
    one AuthAPI metadata request, stores the hydrated metadata in the
    in-memory auth object, and redacts the secret from debug output. Legacy
    Agent Identity JWT handling remains unchanged. The shared access-token
    classifier lives in a private neutral module because it dispatches
    between both credential types.
    
    PAT hydration fails closed when AuthAPI omits any required metadata,
    including email. Hydrated metadata is intentionally not persisted:
    startup performs a live `whoami` preflight so revoked tokens or changed
    account metadata are not accepted from a stale cache.
    
    ## Workspace restriction scope
    
    This change intentionally does **not** apply
    `forced_chatgpt_workspace_id` to PAT authentication. The setting is a
    client-side config guardrail, not an authorization boundary, and PAT
    does not currently require workspace-ID parity. The PAT login and
    `CODEX_ACCESS_TOKEN` paths therefore validate through AuthAPI without
    threading workspace-restriction state through access-token loading.
    Existing workspace checks for non-PAT auth remain on their established
    paths.
    
    ## App-server compatibility
    
    The public app-server `AuthMode` is shared across v1 and v2, and
    PAT-backed auth reports `personalAccessToken` through both APIs.
    Following human review, this intentionally removes the temporary v1
    compatibility mapping that reported PATs as `chatgpt`; the deprecated v1
    API is kept in parity with v2 rather than maintaining a separate closed
    enum. Clients with exhaustive auth-mode handling in either API version
    must add the new case and should generally treat it as ChatGPT-backed
    unless they need PAT-specific behavior.
    
    The v1 auth-status response still omits the raw PAT when `includeToken`
    is requested because that response cannot carry the account metadata
    needed to reuse the credential safely. Persisted PAT auth also omits the
    new enum value so older Codex builds can deserialize `auth.json` and
    infer PAT auth from the credential field after a rollback.
    
    ## Validation
    
    Latest review-fix validation:
    
    - `CARGO_INCREMENTAL=0 just test -p codex-login` (126 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli` (263 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli
    stored_auth_validation_handles_personal_access_token`
    - `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol` (226
    passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-models-manager
    refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth`
    - `CARGO_INCREMENTAL=0 just test -p codex-tui
    existing_non_oauth_chatgpt_login_counts_as_signed_in`
    - `CARGO_INCREMENTAL=0 just fix -p codex-login -p
    codex-app-server-protocol -p codex-models-manager -p codex-tui -p
    codex-cli`
    - `just fmt`
    - `git diff --check`
    
    The broader `codex-tui` suite previously compiled and ran 2,834 tests.
    Three unrelated environment-sensitive guardian/IDE-socket tests failed
    after retries; the PAT-relevant TUI coverage passed.
  • [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`
  • [2 of 2] Finish moving goal runtime to extension (#26548)
    ## Stack
    
    1. [#26547](https://github.com/openai/codex/pull/26547) - [1 of 2] Align
    goal extension with core behavior
    2. [#26548](https://github.com/openai/codex/pull/26548) - [2 of 2] Move
    goal runtime to extension
    
    ## Why
    
    This PR completes the switch of the goal behavior to the
    extension-backed runtime and removes the old core goal implementation.
    
    ## What Changed
    
    - Installs the goal extension for app-server `ThreadManager` sessions.
    - Routes app-server thread goal `get`, `set`, and `clear` through
    `GoalService`.
    - Uses thread-idle lifecycle emission after goal resume and snapshot
    ordering so the extension can decide whether to continue the goal.
    - Forwards extension goal updates through a FIFO async app-server
    notification path so backpressure does not drop them or reorder updates.
    - Keeps review turns from enabling goal runtime behavior.
    - Plans extension tools before dynamic tools so built-in goal tool names
    keep their old precedence when goals are enabled.
    - Removes the old core goal runtime, core goal tool handlers, and core
    goal tool specs.
    - Updates tests that were coupled to the core-owned goal runtime while
    leaving the legacy `<goal_context>` compatibility path in core for old
    threads.
    - Removes the stale cargo-shear ignore now that `codex-goal-extension`
    is used by the workspace.
    - Keeps realtime event matching exhaustive after removing the old
    goal-specific realtime text path.
    
    
    ## Validation
    
    - Ran manual `/goal` runs in TUI. Validated time accounting matched
    wall-clock time and goal lifecycle state transitions.
  • [codex] Respect Windows sandbox backend in exec policy (#26307)
    ## Why
    
    Windows managed filesystem permissions can now be backed by a real
    Windows sandbox. `exec-policy` was still treating the managed read-only
    policy shape as if there were never a sandbox backend, so benign
    unmatched commands such as PowerShell directory listings could be
    rejected with `blocked by policy` even when `windows.sandbox` was
    enabled.
    
    The inverse case still needs to stay conservative: when the Windows
    sandbox backend is disabled, managed filesystem restrictions are only
    configuration intent, not an enforced filesystem boundary. That applies
    to writable-root restricted profiles too, not just read-only profiles.
    
    ## What Changed
    
    - Thread the effective `WindowsSandboxLevel` into exec-policy approval
    decisions for shell, unified exec, and intercepted shell exec paths.
    - Treat managed restricted filesystem profiles as lacking sandbox
    protection only on Windows when `WindowsSandboxLevel::Disabled`.
    - Exclude full-disk-write profiles from that no-backend path because
    they do not rely on filesystem sandbox enforcement.
    - Remove the cwd-sensitive read-only heuristic and the now-stale cwd
    plumbing from exec-policy approval contexts.
    - Add Windows coverage for both enabled-sandbox and disabled-backend
    behavior, including a writable-root managed profile.
    
    ## Validation
    
    - Added/updated `exec_policy` coverage for managed filesystem
    restrictions, full-disk-write exclusion, enabled Windows sandbox
    behavior, and disabled-backend read-only/writable-root behavior.
    - `just test -p codex-core exec_policy` — 100 passed, 10 leaky
    - Empirical local `codex exec` probe with `--sandbox read-only -c
    'windows.sandbox="unelevated"'`: PowerShell directory listing completed
    successfully.
    - Disabled-backend control with Windows sandbox cleared: the same
    command was rejected with `blocked by policy`.
  • Make turn diff tracker multi-env aware (#26433)
    ## Why
    
    Turn diffs were tracked as one flat set of absolute paths. In
    multi-environment turns, local and remote environments can report the
    same path while representing different filesystems, so a single path key
    can collapse distinct changes or attribute them to the wrong
    environment.
    
    The environment name is **NOT** included in the generated unified diff.
    This can come later.
  • Require absolute cwd in thread settings (#26532)
    ## Why
    
    Thread settings cwd overrides are expected to be resolved before they
    enter core. Keeping this boundary as a plain `PathBuf` made it easy for
    core/session code to keep fallback normalization and relative-path
    resolution logic in places that should only receive an already-resolved
    cwd.
    
    This is intentionally the absolute-cwd-only slice: it does not change
    environment selection stickiness or cwd-to-default-environment fallback
    behavior.
    
    ## What changed
    
    - Changes `ThreadSettingsOverrides.cwd`,
    `CodexThreadSettingsOverrides.cwd`, and `SessionSettingsUpdate.cwd` to
    use `AbsolutePathBuf`.
    - Removes core-side cwd normalization/resolution from session settings
    updates.
    - Updates affected core/app-server test helpers and callsites to pass
    existing absolute cwd values or use `abs()` helpers.
    
    ## Validation
    
    Opening as draft so CI can start while local validation continues.
  • Encrypt multi-agent v2 message payloads (#26210)
    ## Why
    
    Multi-agent v2 currently routes agent instructions through normal tool
    arguments and inter-agent context. That means the parent model can emit
    plaintext task text, Codex can persist it in history/rollouts, and the
    recipient can receive it as ordinary assistant-message JSON.
    
    This changes the v2 path so agent instructions stay encrypted between
    model calls: Responses encrypts the `message` argument returned by the
    model, Codex forwards only that ciphertext, and Responses decrypts it
    internally for the recipient model.
    
    ## What changed
    
    - Mark the v2 `message` parameter as encrypted for `spawn_agent`,
    `send_message`, and `followup_task`.
    - Treat multi-agent v2 tool `message` values as ciphertext
    unconditionally.
    - Store v2 inter-agent task text in
    `InterAgentCommunication.encrypted_content` with empty plaintext
    `content`.
    - Convert encrypted inter-agent communications into the Responses
    `agent_message` input item before sending the child request.
    - Preserve `agent_message` items across history, rollout, compaction,
    telemetry, and app-server schema paths.
    - Leave multi-agent v1 unchanged.
    
    ## Message shape
    
    The model still calls the v2 tools with a `message` argument, but that
    value is now ciphertext:
    
    ```json
    {
      "name": "spawn_agent",
      "arguments": {
        "task_name": "worker",
        "message": "<ciphertext>"
      }
    }
    ```
    
    Codex stores the task as encrypted inter-agent communication:
    
    ```json
    {
      "author": "/root",
      "recipient": "/root/worker",
      "content": "",
      "encrypted_content": "<ciphertext>",
      "trigger_turn": true
    }
    ```
    
    When Codex builds the recipient request, it forwards the ciphertext
    using the new Responses input item:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root",
      "recipient": "/root/worker",
      "content": [
        {
          "type": "encrypted_content",
          "encrypted_content": "<ciphertext>"
        }
      ]
    }
    ```
    
    Responses decrypts that item internally for the recipient model.
    
    ## Context impact
    
    - Parent context no longer carries plaintext v2 agent task instructions
    from these tool arguments.
    - Codex rollout/history stores ciphertext for v2 agent instructions.
    - Recipient requests receive an `agent_message` item instead of
    assistant commentary JSON for encrypted task delivery.
    - Plaintext completion/status notifications are still plaintext because
    they are Codex-generated status messages, not encrypted model tool
    arguments.
    
    ## Validation
    
    - `just test -p codex-tools`
    - `just test -p codex-protocol`
    - `just test -p codex-rollout`
    - `just test -p codex-rollout-trace`
    - `just test -p codex-otel`
    - `just write-app-server-schema`
  • [codex] Add use_responses_lite 'override' logic (#26487)
    ## Summary
    
    - add a defaulted `ModelInfo.use_responses_lite` catalog field
    - support serializing `reasoning.context` while preserving the existing
    effort and summary path
    - has not been turned on for any models yet
    
    I've added an override to parallel tools if responses_lite is on. I've
    also forced persistent reasoning when using responses_lite. It would be
    ideal if we could centralize all the responses_lite plumbing, but I
    think this is best for now to keep the plumbing & diffs small.
    
    ## Testing
    
    - `cargo test -p codex-protocol
    model_info_defaults_availability_nux_to_none_when_omitted`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-core
    responses_lite_sets_all_turns_context_and_disables_parallel_tool_calls`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-core
    configured_reasoning_summary_is_sent`
    - `cargo check -p codex-core --tests`
    - `RUST_MIN_STACK=8388608 cargo clippy -p codex-core --tests` (passes
    with pre-existing warnings in `codex-code-mode` and
    `codex-core-plugins`)
  • [codex] Emit sandbox outcome telemetry event (#25955)
    ## Summary
    
    Adds a dedicated `codex.sandbox_outcome` telemetry event so we can query
    sandbox edge outcomes without threading sandbox metadata through
    tool-result output types.
    
    This is meant to make sandbox failures and approved escalation retries
    visible in OTEL while keeping the existing `codex.tool_result` event
    shape focused on tool completion data.
    
    ## What changed
    
    - Adds `SessionTelemetry::sandbox_outcome(...)`, which emits
    `codex.sandbox_outcome` as both a log and trace event.
    - Records the tool name, call id, sandbox outcome, initial attempt
    duration, and escalated attempt duration when a retry runs.
    - Emits `denied` when the sandbox blocks execution and no retry is run.
    - Emits `timed_out` and `signal` when those sandbox errors surface from
    tool execution.
    - Emits `escalated` when the initial sandboxed attempt fails and the
    approved unsandboxed retry succeeds.
    - Adds OTEL coverage for the new event payload, including timing fields.
    
    ## Validation
    
    - `RUST_MIN_STACK=8388608 just test -p codex-core
    sandbox_outcome_event_records_outcome
    handle_sandbox_error_user_approves_retry_records_tool_decision`
    - `just test -p codex-otel
    otel_export_routing_policy_routes_tool_result_log_and_trace_events
    runtime_metrics_summary_collects_tool_api_and_streaming_metrics`
    - `just fix -p codex-core`
    - `just fix -p codex-otel`
  • ci: test windows cross build (#25000)
    We cross build when using bazel for windows. This causes a couple
    hiccups in that v8 does a mksnapshot step that is expecting to snapshot
    on the host arch which wasn't matching when we were doing the
    crossbuild. This was causing segfault failiures when starting up
    codemode from a cross built artifact.
    
    This changes things such that we cross build the library and then run
    and link a snapshot on the host machine/arch which is windows. This
    gives us a functional snapshot and library that can start code-mode on
    windows.
    
    This fixes the build and then fixes two test regressions we had.
  • [codex] Preserve logical paths during AGENTS.md discovery (#26465)
    ## Intent
    
    Follow up on #26205 by avoiding unnecessary filesystem canonicalization
    during `AGENTS.md` discovery. The configured working directory is
    already absolute, and canonicalization incorrectly switches symlinked
    workspaces from their logical parent hierarchy to the target's
    hierarchy.
    
    ## User-facing behavior
    
    For a symlinked working directory such as:
    
    ```text
    test-root/
    |-- logical-repo/
    |   |-- AGENTS.md              ("logical parent doc")
    |   `-- workspace ------------> physical-repo/workspace/
    `-- physical-repo/
        |-- AGENTS.md              ("physical parent doc")
        `-- workspace/
            `-- AGENTS.md          ("workspace doc")
    ```
    
    Before this change, Codex canonicalized `logical-repo/workspace` to
    `physical-repo/workspace` before discovery. It therefore loaded
    `physical-repo/AGENTS.md` and `physical-repo/workspace/AGENTS.md`,
    ignoring the instructions from the repository through which the user
    entered the workspace.
    
    After this change, ancestor discovery walks the configured logical path,
    so Codex loads `logical-repo/AGENTS.md`. Opening
    `logical-repo/workspace/AGENTS.md` still follows the symlink through the
    host filesystem, so the workspace document is also loaded.
    `physical-repo/AGENTS.md` is not loaded.
    
    ## Implementation
    
    Use the logical absolute working directory when discovering project
    instructions and reporting instruction sources. Filesystem reads still
    follow the working-directory symlink, so an `AGENTS.md` in the target
    workspace continues to load while ancestor discovery uses the symlink's
    parents.
    
    ## Validation
    
    Added integration coverage proving that discovery loads the logical
    parent's instructions and the target workspace's instructions, but not
    the target parent's instructions.
  • [codex] Support model-defined reasoning efforts (#26444)
    ## Summary
    - accept non-empty model-defined reasoning effort values while
    preserving built-in effort behavior
    - propagate the non-Copy effort type through core, app-server, TUI,
    telemetry, and persistence call sites
    - preserve string wire encoding and expose an open-string schema for
    clients
    - update model selection and shortcut behavior for model-advertised
    effort values
    
    ## Root cause
    `ReasoningEffort` gained a string-backed custom variant, so it could no
    longer implement `Copy` or rely on derived closed-enum serialization.
    Existing consumers still moved effort values from shared references and
    assumed a fixed built-in value set.
    
    ## Validation
    - `just fmt`
    - Local tests and compilation were not run per request; relying on CI.
  • Remove response.processed websocket request (#26447)
    ## Why
    
    The Responses websocket client no longer needs to send a follow-up
    `response.processed` request after a turn response has already been
    recorded. Keeping that extra acknowledgement path adds feature-gated
    control flow and a second websocket request shape that no longer carries
    useful behavior.
    
    ## What Changed
    
    - Removed the `response.processed` websocket request type and sender.
    - Removed the `responses_websocket_response_processed` feature flag and
    schema entry.
    - Removed turn and remote-compaction plumbing that only tracked response
    IDs to send the acknowledgement.
    - Removed tests that existed solely to cover the deleted feature path.
    
    ## Validation
    
    - `just fix -p codex-core -p codex-api -p codex-features`
  • Route AGENTS.md loading through environment filesystems (#26205)
    ## Why
    
    Workspace-specific `AGENTS.md` loading needs to use the selected
    environment filesystem so remote workspaces and child agents read
    instructions from their actual environment instead of the host
    filesystem. The app-server should report the same instruction sources
    the initialized thread actually loaded, rather than independently
    rescanning configuration and filesystem state.
    
    ## What changed
    
    - Introduce `LoadedAgentsMd` to retain ordered user, project, and
    internal instructions with their provenance.
    - Load and canonicalize workspace `AGENTS.md` paths through the primary
    `EnvironmentManager` environment, then render the loaded instructions
    when constructing turn context.
    - Expose cached loaded instruction sources from initialized threads and
    use them for app-server start, resume, and fork responses.
    - Preserve global `CODEX_HOME` loading and separator behavior while
    excluding empty project files that did not supply model-visible
    instructions.
    - Add integration coverage for CLI injection, selected-environment
    provenance and rendering, empty environment selection, and cached
    sources on loaded-thread resume.
    
    ## Validation
    
    - `just test -p codex-core agents_md`
    - `just test -p codex-core
    selected_environment_sources_match_model_visible_instructions`
    - `just test -p codex-exec agents_md`
    - `just test -p codex-app-server instruction_sources`
    - `just test -p codex-app-server --status-level fail`
  • core: allow excluding tool namespaces from code mode (#26320)
    ## Why
    
    Research and training setups need to control which tool namespaces
    appear inside code mode's nested `tools` surface without disabling those
    tools entirely. This makes it possible to train against a deliberately
    reduced nested-tool setup while preserving the normal direct and
    deferred tool paths.
    
    ## What
    
    - Extend `features.code_mode` to accept structured configuration while
    preserving the existing boolean syntax.
    - Add an exact `excluded_tool_namespaces` list under
    `[features.code_mode]`:
    
      ```toml
      [features.code_mode]
      enabled = true
      excluded_tool_namespaces = ["mcp__codex_apps", "multi_agent_v1"]
      ```
    
    - Filter matching canonical `ToolName` namespaces when constructing code
    mode's nested router and code-mode-specific direct tool descriptions.
    - Keep excluded tools registered, directly exposed in mixed code mode,
    and discoverable through top-level `tool_search` when otherwise
    eligible.
    - Derive deferred nested-tool guidance after namespace filtering so the
    `exec` description does not advertise excluded-only deferred tools.
    - Preserve the boolean/table representation when materializing config
    locks and update the generated config schema.
    
    ## Testing
    
    - `just test -p codex-features`
    - `just test -p codex-config`
    - `just test -p codex-core load_config_resolves_code_mode_config`
    - `just test -p codex-core
    lock_contains_prompts_and_materializes_features`
    - `just test -p codex-core
    excluded_deferred_namespaces_do_not_enable_nested_tool_guidance`
    - `just test -p codex-core
    code_mode_excludes_configured_nested_tool_namespaces`
    - `cargo check -p codex-thread-manager-sample`
  • log plugin MCP server names (#26002)
    ## Summary
    - emit the plugin capability summary's exact MCP server names in
    `codex_plugin_used`
    
    ## Test
    - `just test -p codex-analytics`
    - `just test -p codex-core
    explicit_plugin_mentions_track_plugin_used_analytics`
    - `just fix -p codex-analytics`
  • Use Windows setup marker as completion signal (#26074)
    # Why
    
    When an organization requires the elevated Windows sandbox, Codex
    launches an elevated helper to provision users, configure firewall and
    ACL rules, and lock persistent sandbox directories.
    
    We observed that closing the helper after setup started could leave the
    machine partially initialized while the TUI still announced **Sandbox
    ready**. Model-only turns continued to work, but the first shell command
    retried setup and failed with Windows cancellation error `1223`.
    
    This was not an enforcement bypass; command execution continued to fail
    closed. The issue was a false readiness signal: `setup_marker.json` was
    written during user provisioning, before the remaining setup stages had
    completed.
    
    # What
    
    Treat `setup_marker.json` as the commit record for Windows sandbox
    setup:
    
    1. Before full or provisioning setup begins, remove the existing marker
    and create the final marker path with a protected ACL.
    2. Keep the marker empty and therefore invalid while setup is in
    progress. Sandbox users cannot read, modify, or replace it.
    3. Run every synchronous setup stage.
    4. After setup succeeds, write the valid marker contents without
    changing its ACL.
    5. After the helper exits successfully, verify the existing readiness
    check before enabling the sandbox.
    
    If setup is canceled or fails, the marker remains invalid and Codex
    reports setup as incomplete instead of announcing readiness.
    
    Refresh-only and read-ACL-only helper runs continue to leave the marker
    untouched. The setup version remains `5` to avoid forcing all existing
    Windows users through elevated setup again.
    
    # Verification
    
    - Added coverage confirming sandbox users cannot read or modify the
    setup marker after elevated setup.
    - Added coverage confirming a successful helper exit without complete
    setup artifacts is rejected.
    - Ran `just test -p codex-windows-sandbox`.
  • Rewrite oversized tool outputs during remote compaction (#26251)
    ## Why
    
    When trying to fit history under compaction limit rewrite output items
    instead of removing them entirely. Otherwise we're breaking
    incrementality in relation to the previous response.
  • feat: catalog multi-agent v2 config (#26254)
    ## Why
    
    Model metadata can now select multi-agent v2 even when a user has not
    enabled `features.multi_agent_v2` in their config. Some existing configs
    still set the legacy `agents.max_threads` knob for v1 multi-agent
    behavior, so treating every v2 runtime as incompatible with
    `agents.max_threads` would break users whose only v2 signal came from
    the model catalog.
    
    The incompatible configuration is specifically enabling
    `features.multi_agent_v2` while also setting `agents.max_threads`.
    Catalog-forced v2 should use the v2 concurrency setting and ignore the
    legacy v1 cap instead of rejecting the config.
    
    ## What changed
    
    - Split config validation from runtime concurrency calculation:
    `effective_agent_max_threads` now just returns the effective cap for the
    resolved multi-agent runtime.
    - Added explicit validation for `features.multi_agent_v2` +
    `agents.max_threads` at session startup.
    - Preserved catalog-selected v2 behavior when `features.multi_agent_v2`
    is disabled, so existing configs with `agents.max_threads` keep
    starting.
    - Updated model-runtime selector coverage so a catalog v2 model still
    exposes v2 tools even when `agents.max_threads` is set and the config
    flag is disabled.
    
    ## Validation
    
    - `cargo check -p codex-core --lib`
    - `just test -p codex-core --lib -E
    "test(multi_agent_v2_feature_rejects_agents_max_threads) |
    test(catalog_v2_allows_agents_max_threads_when_feature_disabled)"`
  • Expose local image paths to models (#25944)
    ## Why
    
    Local image attachments include image bytes, but the adjacent
    model-visible label omits the source path. Exposing the path lets
    model-selected workflows refer back to the intended local image
    explicitly.
    
    ## What changed
    
    - Include an escaped `path` attribute in model-visible local image
    opening tags.
    - Reuse the path-aware marker generator in rollout coverage.
    - Update protocol, replay, and rollout coverage for the new request
    shape.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-protocol`
    - `just test -p codex-core skips_local_image_label_text`
    - `just test -p codex-core
    copy_paste_local_image_persists_rollout_request_shape`
    - `git diff --check`
  • Add remote request permissions integration coverage (#25867)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. This PR (#25867) - Add remote request permissions integration
    coverage: verifies the selected remote environment across request,
    approval, grant reuse, and exec.
    
    This PR is stacked on #25862 and should be reviewed after #25850,
    #25858, and #25862.
    
    ## Why
    
    The environment-scoped permission stack needs one end-to-end check that
    exercises the CCA-shaped path, not only unit-level parsing. This
    verifies that a model-sent `environmentId` on `request_permissions`
    reaches the approval event, stores the grant under the selected
    environment, and is reused by a later tool call in that same
    environment.
    
    ## What Changed
    
    - Adds a remote executor integration test for `request_permissions` with
    `environmentId: remote` and a relative write root.
    - Asserts the permission event reports the remote environment and cwd,
    and that the normalized grant resolves under the remote cwd.
    - Approves the grant, then runs a remote `exec_command` without explicit
    per-call permissions and verifies it completes without another exec
    approval and writes only in the remote filesystem.
    
    ## Verification
    
    - Not run locally per instruction.
    - `git diff --check`
  • [codex] Keep hosted tools visible in code-only mode (#25890)
    ## Why
    
    `code_mode_only` moved ordinary runtime tools behind `exec`, but it also
    hid hosted Responses tools. Hosted `web_search` and `image_generation`
    do not have a nested `exec` runtime path, so code-only sessions lost
    those capabilities entirely even when their existing provider, auth,
    model, and configuration gates passed.
    
    ## What changed
    
    - Keep hosted Responses tools top-level in `code_mode_only` sessions
    after their existing gates pass.
    - Preserve the existing nested-tool behavior for ordinary runtimes and
    the direct-only behavior for multi-agent v2 tools.
    - Add planner coverage for `code_mode_only` with default multi-agent v2
    settings, hosted live web search, and hosted image generation.
    
    ## Verification
    
    - Added focused regression coverage in
    `codex-rs/core/src/tools/spec_plan_tests.rs`.
    - Left execution to CI per repository workflow.
  • Switch runtime to cloud config bundle (#24622)
    ## Summary
    
    - Adapts the moved `codex-cloud-config` crate from the legacy cloud
    requirements endpoint to the new config bundle endpoint.
    - Switches runtime consumers from `CloudRequirementsLoader` to
    `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
    config and requirements.
    - Removes the legacy cloud requirements domain loader path.
    
    ## Details
    
    This intentionally keeps `codex-cloud-config` monolithic for review
    lineage: the previous PR establishes the crate move, and this PR shows
    the behavior change against that moved implementation. A follow-up PR
    splits the module back into focused files.
    
    The new bundle path preserves the important cloud requirements loader
    semantics where intended: account-scoped signed cache, 30 minute TTL, 5
    minute refresh cadence, retry/backoff, auth recovery, and fail-closed
    startup loading. The cached payload changes from a single requirements
    TOML string to the backend-delivered bundle, and validation rejects
    malformed config or requirements fragments before cache write/use.
  • flake: Keep plugin test homes alive (#25857)
    ## Summary
    
    Keep the full `TestCodex` harness alive in plugin integration tests
    instead of returning only the `CodexThread`.
    
    ## Why
    
    The helper was moving a temporary `codex_home` into `TestCodex`, then
    immediately dropping the harness and returning only the thread. For
    plugin MCP tests, the MCP server cwd is inside that temporary home. If
    the temp directory is removed while MCP startup is still racing, the
    server launch can fail with `No such file or directory`.
    
    Keeping the harness in scope keeps the temp home alive for the test
    duration and removes the lifetime race behind the recent
    `explicit_plugin_mentions_inject_plugin_guidance` flake.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core
    explicit_plugin_mentions_inject_plugin_guidance`
  • Test runtime selector before first turn (#25724)
    Stack split from #25708. Original PR intentionally left open. This fifth
    PR adds coverage that a remotely selected multi-agent runtime is applied
    when the model is selected before the first turn.
  • Test remote multi-agent runtime selector override (#25723)
    Stack split from #25708. Original PR intentionally left open. This
    fourth PR adds coverage that remote model multi-agent runtime selectors
    override local feature flag defaults.