7919 Commits

  • [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] Report unusable MCP OAuth credentials as logged out (#26713)
    ## Why
    
    Persisted MCP OAuth credentials were reported as authenticated whenever
    a credential record existed. An expired token without a usable refresh
    token could therefore appear as `OAuth` even though startup could not
    authenticate with it, leaving users with a misleading status instead of
    a login prompt.
    
    ## What changed
    
    - Classify stored OAuth credentials as missing, usable, or requiring
    authorization.
    - Reuse the existing refresh window so near-expiry credentials without a
    refresh path are also treated as logged out.
    - Validate required credential fields before reporting OAuth
    authentication.
    - Add unit coverage for credential usability and integration coverage
    for expired, unexpired, and refreshable persisted credentials.
    
    ## Validation
    
    - `just test -p codex-rmcp-client`
  • [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.
  • Route hosted Apps MCP through extensions (#27191)
    ## Stack
    
    - Base: #27184
    - This PR is the second vertical and should be reviewed against
    `jif/external-plugins-1`, not `main`.
    
    ## Why
    
    CCA is moving toward a split runtime where the orchestrator may have no
    filesystem or executor, but it still needs to activate remotely hosted
    plugin components. HTTP MCP servers are the simplest complete example:
    they need configuration and host authentication, but they do not need an
    executor process.
    
    The Apps MCP endpoint is currently synthesized by a special-purpose
    loader inside the MCP runtime. That works locally, but it leaves hosted
    MCP activation outside the extension model being established in #27184.
    It also makes the Apps path a poor foundation for plugins whose skills,
    MCP servers, connectors, and hooks may come from different sources or
    execute in different places.
    
    This PR moves that one behavior behind an extension-owned contribution
    while preserving the existing local fallback. It deliberately does not
    introduce a generic plugin activation framework.
    
    ## What changed
    
    ### MCP extension contribution
    
    `codex-extension-api` gains an ordered `McpServerContributor` contract.
    A contributor returns typed `Set` or `Remove` overlays for MCP server
    configuration; later contributors win for the names they own.
    
    The contract stays at the existing MCP configuration boundary.
    Extensions do not create a second connection manager or transport
    abstraction.
    
    ### Hosted Apps MCP extension
    
    A new `codex-mcp-extension` contributes the reserved `codex_apps` server
    from the existing Apps feature, ChatGPT base URL, path override, and
    product SKU configuration.
    
    When `apps_mcp_path_override` is enabled for `https://chatgpt.com`, the
    resulting streamable HTTP endpoint is
    `https://chatgpt.com/backend-api/ps/mcp`. The existing ChatGPT-auth gate
    remains authoritative, so this server can run in an orchestrator-only
    process without being exposed for API-key sessions.
    
    ### One resolved runtime view
    
    `McpManager` now distinguishes three views:
    
    - **configured:** config- and plugin-backed servers before extension
    overlays;
    - **runtime:** configured servers plus host-installed extension
    contributions;
    - **effective:** runtime servers after auth gating and compatibility
    built-ins.
    
    App-server installs the hosted MCP extension and uses the runtime view
    for thread startup, refresh, status, threadless resource reads,
    connector discovery, and MCP OAuth lookup. This keeps
    `mcpServer/oauth/login` consistent with the servers exposed by the other
    MCP APIs. The hosted Apps server itself continues to use existing
    ChatGPT host authentication rather than MCP OAuth.
    
    ## Compatibility
    
    Hosts that do not install the MCP extension retain the existing Apps MCP
    synthesis path. This preserves current local-only, CLI, and
    standalone-host behavior while app-server exercises the extension path.
    
    Disabling Apps removes the reserved `codex_apps` entry, and losing
    ChatGPT auth removes it from the effective runtime view. Executor
    availability is not consulted for this HTTP transport.
    
    ## Follow-ups
    
    The next vertical will resolve a manifest-declared stdio MCP server from
    an executor-selected plugin root and execute it in the environment that
    owns that root. Later verticals can add backend-owned skills, connector
    metadata, hooks, durable selection semantics, and incremental local
    convergence without changing the component-specific runtime boundaries
    introduced here.
    
    ## Verification
    
    Focused coverage was added for:
    
    - contributing the hosted Apps MCP at `/backend-api/ps/mcp` without an
    executor;
    - requiring ChatGPT auth in the effective runtime view;
    - removing a reserved configured Apps server when the Apps feature is
    disabled.
    
    `cargo check -p codex-app-server -p codex-mcp-extension -p
    codex-extension-api -p codex-mcp` passed. Tests and Clippy were not run
    locally under the current development instruction; CI provides the full
    validation pass.
  • [1/4] Add Python goal routing foundation (#27110)
    ## Why
    
    Goal continuation turns are emitted by the existing runtime as separate
    physical turns. The Python SDK needs private thread-scoped routing
    before it can present those notifications as one logical operation,
    without changing ordinary turn routing or the app-server protocol.
    
    ## What
    
    - add private goal operation state and thread-scoped notification
    routing
    - add internal wrappers for the existing `thread/goal/clear` and
    `thread/goal/set` RPCs
    - include existing goal notifications in the SDK notification union
    - preserve ordinary turn-ID routing unchanged
    - add focused routing coverage
    
    This PR does not expose a public goal API. It is the first PR in the
    Python goal operations stack.
    
    ## Test plan
    
    - online CI, including the Python SDK suite
    - focused typed-notification routing coverage
  • Reduce TUI legacy core dependencies (#26711)
    ## Why
    
    The TUI still reached through `app-server-client::legacy_core` for
    thread-name normalization and project-instruction filename details. In
    particular, checking the TUI's local filesystem for `/init` is incorrect
    for remote app-server sessions, where the server owns the working
    directory and instruction discovery.
    
    ## What changed
    
    - use the instruction source paths supplied by the app server to decide
    whether `/init` should avoid overwriting project instructions
    - keep the small thread-name normalization helper local to the TUI
    - remove the now-unused instruction filename constants, utility module,
    and other unused `legacy_core` re-exports
    - make status helper tests independent of concrete instruction filenames
    
    ## Verification
    
    - `just test -p codex-app-server-client`
    - `just test -p codex-tui
    slash_init_skips_when_project_instructions_are_loaded`
    - `just test -p codex-tui` ran 2,799 tests; 2,797 passed and two
    unrelated guardian feature-flag tests failed reproducibly in untouched
    code
    
    ### Manual test
    
    Started an app server over WebSocket with a remote workspace containing
    `AGENTS.md`, then connected the TUI using `--remote`. After confirming
    `thread/start` returned the file in `instructionSources`, deleted
    `AGENTS.md` and ran `/init` in the existing session.
    
    The TUI still reported that project instructions already existed and
    skipped `/init`. The trace contained no `turn/start` request, confirming
    the decision came from app-server session state rather than a new
    client-local filesystem check.
  • Allow creating a new goal after completion (#26681)
    ## Why
    
    Users have indicated that they want an agent to be able to create a new
    goal for itself after completing the previous goal. Currently, that's
    not possible because agents cannot overwrite an existing goal even if
    it's complete. This PR removes this limitation and allows `create_goal`
    to overwrite an existing goal if it is in the `complete` state.
    
    ## What changed
    
    `create_goal` now replaces the existing goal only when its status is
    `complete`. The replacement is performed atomically in the goal store,
    creates a fresh active goal with reset usage, and continues to reject
    creation while any unfinished goal exists. App server clients see a
    single `thread/goal/updated` event when the previous goal is replaced
    with the new one.
    
    The tool description and error message now reflect these semantics.
    
    ## What didn't change
    
    Agents are not allowed to create a new goal (overwrite their existing
    goal) if an existing goal is still active, blocked, paused, or in any
    other state other than "completed".
  • Add SOCKS5 TCP MITM coverage (#22685)
    ## Summary
    - reuse the MITM HTTPS serving path for raw SOCKS5 TCP streams
    - route limited-mode and hooked SOCKS5 TCP requests through MITM before
    dialing upstream
    - keep SOCKS5 UDP limited-mode behavior unchanged
    
    ## Validation
    - `just fmt`
    - `just test -p codex-network-proxy`
    - `just fix -p codex-network-proxy`
    - `git diff --check`
  • [codex] Speed up local nextest runs (#26479)
    ## Why
    
    `just test` currently uses the CI-oriented nextest profile, which
    serializes app-server integration tests even on developer machines that
    can run several safely. Bounded local parallelism substantially shortens
    this common iteration loop without changing CI behavior.
    
    Eight-worker experiments were faster, but keeping them reliable required
    relaxing several test deadlines. Four workers for integration tests is a
    solid tradeoff that speeds up local testing without needing to change
    test logic.
    
    ## What changed
    
    - Add a `local` nextest profile that inherits the existing defaults.
    - Allow up to four app-server integration tests to run concurrently
    under that profile.
    - Make `just test` select the local profile on Unix and Windows.
    - Keep the default CI profile serialized and leave all test deadlines
    unchanged.
    
    The tests use separate processes, randomized temporary `CODEX_HOME`
    directories, and ephemeral ports. The remaining shared constraints are
    system resources; each app-server also uses a multi-thread Tokio
    runtime, and fuzzy-search tests can create additional worker threads, so
    the local cap remains intentionally conservative.
    
    ## Performance and validation
    
    All measurements below are warm, execution-only app-server runs with
    nextest retries disabled.
    
    On the current rebased branch, an AMD EPYC 7763 machine with 16 logical
    CPUs and 62 GiB RAM completed three consecutive runs:
    
    | Run | Nextest time | Wall time | Result |
    | --- | ---: | ---: | --- |
    | 1 | 142.941s | 145.17s | 836/836 passed |
    | 2 | 143.402s | 145.59s | 836/836 passed |
    | 3 | 142.870s | 145.08s | 836/836 passed |
    
    The mean wall time was 145.28s. The slow-inventory, approval replay, and
    zsh-fork tests all passed with their original deadlines.
    
    Earlier measurements on the same Linux machine, before the suite grew,
    showed the scaling that motivated the change:
    
    | App-server concurrency | Nextest time | Result |
    | --- | ---: | --- |
    | 1 | 369.5s | 572/572 passed |
    | 2 | 194.5s | 572/572 passed |
    | 4 | 111.0s mean over 3 runs | 3/3 clean |
    
    Four workers reduced that execution time by about 70%, a roughly 3.3x
    speedup over serialization.
  • [codex-analytics] add extensible feature thread sources (#27063)
    ## Why
    - `ThreadSource` currently defines a closed set of core-owned values
    - Product features also create threads for background or scheduled work
    - Adding every product-specific value to the core enum would require
    repeated `codex-rs` protocol changes
    - Feature-backed values let product callers provide precise attribution
    while preserving the existing core classifications
    
    ## What Changed
    - Adds `ThreadSource::Feature(String)` for app-owned thread source
    values
    - Represents all app-server v2 thread sources as scalar strings, so a
    feature source is supplied as `"automation"`
    - Persists and emits the feature's plain string label, so `"automation"`
    produces `thread_source="automation"` in analytics
    - Keeps `user`, `subagent`, and `memory_consolidation` as explicit
    core-owned values and regenerates the app-server schemas and TypeScript
    bindings
    
    ## Verification
    - `just write-app-server-schema`
    - `cargo check --workspace`
    - `just test -p codex-protocol
    feature_thread_source_serializes_as_its_app_owned_label`
    - `just test -p codex-app-server-protocol
    thread_sources_round_trip_as_scalar_labels`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `just fmt`
  • [codex] Test extension API contracts (#26835)
    ## Why
    
    `codex-extension-api` defines contracts shared by extension crates and
    their hosts, but it had no direct test suite. Host and feature tests
    cover downstream behavior, while regressions in the API crate's own
    typed state, registry ordering, and capability adapters could go
    unnoticed.
    
    ## What
    
    - Add public-surface integration tests for `ExtensionData`, including
    concurrent initialization and poison recovery.
    - Cover contributor registration order, approval short-circuiting, event
    sink retention, no-op response injection, and closure-based agent
    spawning.
    - Add the test-only dependencies used by the suite.
    
    ## Validation
    
    - `just test -p codex-extension-api`
    - `just argument-comment-lint -p codex-extension-api`
    - `just bazel-lock-check`
  • 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.
  • app-server: reject direct input to multi-agent v2 sub-agents (#27173)
    ## Why
    
    Multi-agent v2 sub-agents are owned and coordinated by their parent
    agent. Allowing an app-server client to start or steer turns on a
    spawned child bypasses the multi-agent messaging path and creates a
    second, conflicting source of work for that sub-agent.
    
    ## What changed
    
    - Reject direct `turn/start` and `turn/steer` requests targeting
    multi-agent v2 thread-spawn sub-agents.
    - Identify these targets using both the thread's resolved multi-agent
    version and its `SubAgentSource::ThreadSpawn` session source, leaving
    root threads, v1 agents, and other sub-agent types unchanged.
    - Return a consistent invalid-request error before validating or
    applying the submitted input.
    
    ## Testing
    
    - Added an app-server integration test that spawns a real multi-agent v2
    child and verifies that direct `turn/start` and `turn/steer` requests
    are rejected.
  • fix: Prevent /review crash when entering Esc on steer message (#22879)
    This changes the `/review` escape path so `Esc` no longer behaves like
    the normal queued-follow-up interrupt flow while a review is running.
    Steering is not currently supported in `/review` mode, without this
    change users are able to attempt a steer but it leads to a crash (see
    #22815). If the user has already tried to send additional guidance
    during `/review`, the TUI now keeps the review running and shows a
    warning that steer messages are not supported in that mode, while still
    pointing users to `Ctrl+C` if they actually want to cancel. It also adds
    regression coverage for the review-specific warning behavior. When users
    do cancel with Ctrl+C during /review, the TUI now tolerates the
    active-turn race that can happen during review handoff, and any queued
    steer messages are restored to the composer instead of being discarded.
    
    - Special-case `Esc` during an active `/review` when follow-up steer
    input is pending or has already been deferred.
    - Show a clear warning instead of interrupting the running review.
    - Make the Ctrl+C cancel path during /review resilient to active-turn
    races, while preserving any queued steer text by restoring it to the
    composer.
    - Add review-mode test coverage for the warning path.
    
    ## Testing
    
    1. Start a `/review` with a diff large enough that the review stays
    active for more than a few seconds.
    
    2. While the review is still running, type a follow-up / steer message,
    submit it, and then press `Esc`.
       Before: `Esc` causes the TUI to close abruptly.  
    After: the review keeps running and the transcript shows a warning that
    steer messages are not supported during `/review`, with guidance to use
    `Ctrl+C` if you want to cancel.
    
    3. Press `Ctrl+C` if you actually want to stop the review.  
    Before: (after restarting the test since Pt. 2 crashed) this is the
    intentional cancellation path.
    After: this remains the intentional cancellation path, and any queued
    follow-up steer text is restored to the composer instead of being lost.
       
    ## Note:
    `/review` mode explicitly does not support steering at this time (as
    noted in `turn_processer.rs`, if we want to explore that in the future
    this code will need to be modified). This change keeps unsupported steer
    attempts from crashing the TUI and preserves queued follow-up text if
    the user cancels with Ctrl+C.
  • Avoid rereading rollout history during cold resume (#27031)
    ## Summary
    
    - reuse the history-bearing `StoredThread` loaded while probing for a
    running thread
    - avoid rereading and reparsing the rollout when that probe finds no
    active process
    - reload after shutting down a loaded thread because shutdown may flush
    newer rollout items
    - add a regression test that verifies cold resume performs one
    history-bearing store read
    
    ## Problem
    
    `thread/resume` first reads the persisted thread with history while
    checking whether the thread is
    already running. When no running process exists, cold resume currently
    falls through to
    `resume_thread_from_rollout`, which reads and parses the same history
    again.
    
    That duplicate work grows with rollout size and remains on the
    synchronous resume path even when
    the caller requests `excludeTurns`.
    
    ## Background
    
    The duplicate read was introduced by #24528, which fixed resume
    overrides for idle cached
    threads. To support resumes specified by rollout path,
    `resume_running_thread` began loading the
    stored thread with history so it could resolve the canonical thread ID
    and determine whether a
    cached `CodexThread` was already loaded.
    
    That history is needed when the loaded-thread path handles the request.
    On a cold miss, however,
    the function's boolean result could only report that no loaded thread
    handled the request. It
    discarded the history-bearing `StoredThread`, and the normal cold-resume
    path immediately loaded
    and parsed the same rollout again.
    
    This change preserves the idle cached-thread behavior from #24528 while
    allowing the cold-resume
    path to reuse the probe result.
    
    ## Performance
    
    I benchmarked real retained rollouts using isolated `CODEX_HOME`
    directories, explicit rollout
    paths, debug builds of the commit and its exact parent, and alternating
    parent/patch order. The
    table below uses `thread/resume` with `excludeTurns: true`; response
    payload sizes were identical.
    
    | Rollout size | Records | Parent median | Patch median | Median paired
    saving |
    | ---: | ---: | ---: | ---: | ---: |
    | 6 MB | 3,574 | 541 ms | 441 ms | 132 ms |
    | 30 MB | 15,220 | 1.505 s | 1.041 s | 701 ms |
    | 60 MB | 31,453 | 2.644 s | 1.742 s | 970 ms |
    | 149 MB | 100,874 | 10.506 s | 7.156 s | 3.350 s |
    | 559 MB | 259,734 | 27.759 s | 16.725 s | 9.836 s |
    
    The absolute saving increases with thread size, as expected when
    removing one complete JSONL
    history read and parse. Total resume time is also content-dependent, so
    the relationship is not
    perfectly linear.
    
    I also tested full-history resume with `excludeTurns: false`. The
    response payload was
    byte-identical between variants, and the same size-dependent improvement
    remained visible:
    
    | Rollout size | Parent median | Patch median | Median paired saving |
    | ---: | ---: | ---: | ---: |
    | 6 MB | 1.052 s | 904 ms | 270 ms |
    | 30 MB | 2.667 s | 1.762 s | 924 ms |
    | 60 MB | 8.464 s | 6.272 s | 3.680 s |
    | 149 MB | 26.719 s | 12.118 s | 14.601 s |
    | 559 MB | 40.359 s | 25.475 s | 16.590 s |
    
    ## Validation
    
    - `just test -p codex-app-server
    cold_thread_resume_reuses_non_local_history_probe`
    - `just fix -p codex-app-server -p codex-thread-store`
    - `just fmt`
  • Avoid no-op backfill state writes (#26420)
    ## Summary
    
    - avoid acquiring SQLite's writer slot when the singleton backfill row
    already exists
    - preserve race-safe repair when the row is missing
    - add regressions for writer contention and missing-row repair
    
    ## Why
    
    State runtime initialization and backfill-state reads previously
    executed
    `INSERT ... ON CONFLICT DO NOTHING` even in the steady state. SQLite
    still
    enters the writer path for that statement, so TUI and app-server startup
    could
    wait behind another writer for up to the configured five-second busy
    timeout.
    
    ## Validation
    
    - `just test -p codex-state` (134 tests passed)
    - `just fix -p codex-state`
    - `just fmt`
  • [codex] Ignore pending PR review comments (#27080)
    ## Why
    
    The PR babysitter could surface inline comments from a GitHub review
    that was still in the `PENDING` state. That allowed Codex to start
    acting on feedback before the reviewer submitted it.
    
    ## What changed
    
    - Correlate inline comments with their parent review and ignore pending
    reviews and their comments.
    - Remove pending review IDs from saved watcher state so the feedback
    surfaces normally after publication.
    - Update the skill instructions and add regression coverage for the
    draft-to-published transition.
    
    ## Validation
    
    - `python3 -m pytest
    .codex/skills/babysit-pr/scripts/test_gh_pr_watch.py`
    - Skill package validation with `quick_validate.py`
    - Live verification on #26835: the draft comment stayed hidden and
    surfaced after the review was submitted.
  • app-server: clear stale thread watches after v2 agent interruption (#27166)
    ## Why
    
    PR #27007 moved MultiAgentV2 interruption reporting from the legacy
    collaboration close event to `SubAgentActivity::Interrupted`.
    App-server's missing-thread cleanup still ran only for the legacy event,
    so an interrupted child that had already been unloaded could remain
    marked as loaded and running in `ThreadWatchManager`. That leaves thread
    status and running-turn accounting stale, including the count used
    during graceful shutdown.
    
    ## What changed
    
    - Handle `SubAgentActivity::Interrupted` separately in app-server event
    processing.
    - Remove the child's thread watch when `ThreadManager` no longer has
    that thread.
    - Continue forwarding the same completed sub-agent activity notification
    to clients.
    
    ## Testing
    
    - Added a regression test that starts with a running watch for an
    unloaded child, applies the interrupted activity event, and verifies the
    watch is removed, the running count returns to zero, and the client
    notification is still emitted.
  • multi-agent: add path-based v2 activity tracking (#27007)
    ## Why
    
    Multi-agent v2 identifies agents by canonical paths, but its tool
    handlers still emitted the larger legacy collaboration begin/end events
    built around nickname and role metadata. App-server, rollout-trace,
    analytics, and TUI consumers therefore lacked one compact path-based
    completion signal that behaved consistently across live events and
    replay.
    
    The TUI also needs a bounded `/agent` status surface for v2 agents. It
    should use recent local activity for previews, refresh liveness without
    loading full histories, and keep the legacy picker available when no
    path-backed v2 agent is known.
    
    ## What changed
    
    - Replace the v2 `spawn_agent`, `send_message`, `followup_task`, and
    `interrupt_agent` legacy lifecycle emissions with a success-only
    `SubAgentActivity` event. The event records the tool call ID, occurrence
    time, affected thread, canonical agent path, and `started`,
    `interacted`, or `interrupted` kind.
    - Expose the activity as a completion-only app-server v2
    `subAgentActivity` thread item in live notifications and reconstructed
    history, regenerate the protocol schemas, and count it in sub-agent tool
    analytics.
    - Track canonical paths from live activity and loaded-thread metadata in
    the TUI, and render the activity in live and replayed transcripts.
    - Make `/agent` list running path-backed agents with summaries from
    bounded local event buffers. Each summary is capped at 240 graphemes,
    the scan is capped at six recent items, only the last three wrapped
    lines are shown, and command output is omitted. Liveness falls back to
    metadata-only `thread/read` when local turn state is unavailable.
    - Persist the activity as a terminal rollout-trace runtime payload and
    reduce it to the corresponding spawn, send, follow-up, or close
    interaction edge. `interrupt_agent` is classified as a close-edge
    operation.
    - Preserve the legacy picker when no path-backed v2 agent is known.
    
    ## Compatibility
    
    App-server v2 clients that consumed `collabAgentToolCall` begin/end
    pairs for these tools must handle the new completion-only
    `subAgentActivity` item. Legacy v1 collaboration behavior is unchanged.
    
    ## Screenshot
    
    <img width="684" height="288" alt="Screenshot 2026-06-08 at 15 40 47"
    src="https://github.com/user-attachments/assets/194b3cd0-619d-45fb-b587-cf3e2b1b8a1d"
    />
    
    ## Testing
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-rollout-trace`
    - Added focused coverage for activity analytics, terminal trace
    serialization, spawn-edge reduction, `interrupt_agent` classification,
    TUI status rendering without aggregated command output, and clearing
    stale running state after a completed turn.
  • [codex] Return workspace directory installed plugins (#27098)
    ## Summary
    
    - return installed `workspace-directory` remote plugins by default in
    `plugin/installed`
    - keep shared-with-me installed plugins gated behind `plugin_sharing`
    - filter remote installed plugin marketplaces by canonical marketplace
    name instead of coarse workspace scope
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-core-plugins`
    - `just test -p codex-app-server`
    - `just fix -p codex-core-plugins`
    - `just fix -p codex-app-server`
    - `$xin-build` targeted verification:
    - `just test -p codex-core-plugins
    build_remote_installed_plugin_marketplaces_from_cache_filters_by_marketplace_name`
    - `just test -p codex-app-server
    plugin_installed_includes_workspace_directory_without_plugin_sharing`
    - `just test -p codex-app-server
    plugin_installed_includes_remote_shared_with_me_plugins`
    - `just test -p codex-app-server
    plugin_list_omits_shared_with_me_kind_when_plugin_sharing_disabled`
  • Use server app auth requirements for remote plugin install (#27085)
    ## Summary
    - request `includeAppsNeedingAuth=true` when installing remote plugins
    - return backend-provided `app_ids_needing_auth` from the remote install
    client
    - use those app IDs to populate `appsNeedingAuth` without refetching
    accessible apps, with fallback for older responses
    
    ## Testing
    - `just fmt`
    - `just test -p codex-app-server`
    - `just test -p codex-core-plugins`
    - real app-server install/uninstall check with Notion remote plugin
    - subagent review found no blocking issues
  • [codex] preserve fsmonitor for worktree Git reads (#26880)
    Codex forces `core.fsmonitor=false` on internal Git commands so a
    repository cannot select an executable fsmonitor helper. This also
    disables Git's built-in daemon for `status`, `diff`, and `ls-files`,
    turning those worktree reads into full scans in large repositories.
    
    Read the raw effective `core.fsmonitor` value and preserve it only when
    Git interprets it as true and advertises built-in daemon support through
    `git version --build-options`. Query uncommon boolean spellings back
    through Git using the exact effective value. Unset, false, helper paths,
    malformed values, probe failures, and unsupported Git builds continue to
    force `core.fsmonitor=false`.
    
    Centralize this policy in `git-utils` while keeping process execution in
    the existing local and workspace-command adapters. Probe once per
    worktree workflow and reuse the result for its Git commands, including
    the TUI `/diff` path. Metadata-only commands and repository discovery
    remain disabled without probing. Each probe and requested Git process
    keeps its own existing timeout, and the decision is not cached because
    layered and conditional Git configuration can change while Codex runs.
    
    ---------
    
    Co-authored-by: Chris Bookholt <bookholt@openai.com>
  • [codex] Remove remote compaction failure log (#27106)
    ## Why
    
    `log_remote_compact_failure` was the only consumer of the
    compact-request logging payload and most of the token-usage breakdown
    fields. Once that failure log is removed, keeping the surrounding
    carrier types leaves dead plumbing in the compaction path and context
    manager.
    
    ## What changed
    
    - Remove `log_remote_compact_failure`, `CompactRequestLogData`, and the
    v2 wrapper that only fed that log.
    - Let both remote compaction implementations return the original
    compaction error directly.
    - Replace `TotalTokenUsageBreakdown` with a narrow helper that returns
    only the remaining value needed by compaction analytics.
    - Keep `estimate_response_item_model_visible_bytes` private to the
    context manager implementation.
    
    ## Validation
    
    - `cargo check -p codex-core`
  • Preserve cloud requirements across TUI thread resets (#25177)
    Fixes a TUI regression where thread transitions such as `/new` and
    `/clear` could rebuild config without the cloud requirements loader,
    allowing users to fall back to non-cloud-managed settings. The config
    refresh path now preserves cloud requirements during thread
    reinitialization, and config loading is moved off the deep TUI event
    stack to avoid stack-overflow crashes during those reloads.
    
    - Passes the cloud requirements loader through TUI config rebuild paths.
    - Keeps cloud requirements applied for `/new`, `/clear`, `/fork`, side
    conversations, and session picker transitions.
    - Runs config building on a Tokio task so reloads do not occur on the
    deep TUI caller stack.
    - Adds regression coverage that cloud requirements survive
    thread-transition config refreshes.
    
    ## Test/Repro:
      - Start Codex with a cloud requirement applied.
      - Use `/new` or `/clear`.
    - The refreshed/fresh-session config should still include the cloud
    requirements
      
    This can be tested with any config item, at this moment for oai staff
    the easiest item to test is the `mentions_v2` feature. This is currently
    enabled in cloud requirements, but is not enabled by default. As a
    result, prior to these changes that feature is disabled after `/new` or
    `/clear`. Testing the same steps with a binary from this branch should
    not drop the feature enablement.
  • Update web search citation prompt (#27096)
    ## Summary
    
    - Update the web search tool prompt to require Markdown links for cited
    sources.
    - Explicitly tell the model not to use `turnX`-style citations in
    responses.
    
    ## Context
    
    
    https://openai.slack.com/archives/C0AU83S0ZQU/p1780964147777649?thread_ts=1780352049.512299&cid=C0AU83S0ZQU
    
    ## Test plan
    
    - `git diff --check`
    - `python3 scripts/format.py --check` (fails only on Rust formatter
    setup: rustup cannot create temp files under `/home/dev-user/.rustup`;
    Just and Python formatter checks pass when using temp cache dirs)
  • Show effective sandbox modes in /debug-config (#27068)
    ## Summary
    - Render `/debug-config`'s `allowed_sandbox_modes` from the finalized
    permission constraints instead of the raw requirements list.
    - Add regression coverage for configured full-access and external
    sandbox modes being omitted when effective permissions reject them.
    
    ## Details
    `allowed_sandbox_modes` comes from managed requirements, but the final
    permissions can be further constrained by derived validation rules. For
    example, `permissions.filesystem.deny_read` requires sandbox
    enforcement, so modes that disable or externalize Codex's sandbox are
    not actually usable even if they were present in the raw requirements
    TOML.
    
    The debug renderer now enumerates the configured sandbox-mode labels and
    keeps only those accepted by `Config.permissions`. That makes
    `/debug-config` reflect the same effective permission-profile constraint
    path used by runtime config validation, while preserving the existing
    source/provenance display.
    
    ## Validation
    - Added a regression test for effective sandbox-mode filtering in
    `/debug-config`.
  • fix(tui): linkify complete bare URLs with tildes (#27088)
    ## Background
    
    Bare URLs containing `~` in their path are currently only clickable up
    to the tilde in the interactive TUI. For example, Codex renders the
    visible text for:
    
    
    `https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
    
    but the OSC 8 destination stops at `https://www.cs.tufts.edu/`. This
    makes Cmd-click open the wrong location even though the terminal
    recognizes the complete URL outside Codex.
    
    Fixes #26774.
    
    ## Root Cause
    
    The URL scanner already accepts `~`. The truncation happens earlier:
    with strikethrough parsing enabled, `pulldown-cmark` splits this URL
    into adjacent decoded `Event::Text` values around the tilde. The
    Markdown renderer annotated each text event independently, so only the
    first event still looked like a complete URL with a supported scheme.
    
    The renderer now merges adjacent decoded text events before URL
    annotation. It preserves the combined source range while retaining
    parser-decoded contents, which avoids regressing entities such as
    `&amp;`.
    
    ## Changes
    
    - Add a small iterator that merges adjacent decoded Markdown text events
    and their source ranges.
    - Apply it at the Markdown renderer boundary before hyperlink detection.
    - Add regression coverage for the reported URL in prose, wrapped table
    output, and entity-decoded URLs.
    
    ## How to Test
    
    1. Run Codex with `just c`.
    2. Ask the assistant to output this exact bare URL with no Markdown link
    syntax:
    
    `https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
    3. Hold Cmd and hover or click the URL.
    4. Confirm the complete URL, including the suffix after `~`, is one
    destination.
    5. Repeat with the URL inside a Markdown table and confirm wrapped
    portions retain the same complete destination.
    
    Targeted tests:
    
    - `just test -p codex-tui url_with_tilde`
    - `just test -p codex-tui merged_text_events_preserve_entity_decoding`
    
    The full `codex-tui` test run was also executed. Its only failures were
    the two existing Guardian feature-flag tests:
    
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
  • Add typed file URIs (#26840)
    ## Why
    
    Codex needs stable `file:` URI identifiers that can cross process and
    operating-system boundaries without eagerly interpreting them as native
    paths. Existing fields also need to keep accepting absolute path strings
    during migration.
    
    ## What changed
    
    - Add `codex-utils-path-uri` with a validated, immutable `PathUri`
    wrapper that currently accepts only `file:` URLs.
    - Expose URI-level `basename`, `parent`, and `join` operations that
    preserve authorities and percent encoding without guessing the source
    operating system.
    - Keep native conversion explicit through `AbsolutePathBuf` and the
    current host rules.
    - Serialize as canonical URI text while accepting both URI text and
    legacy absolute native paths during deserialization.
    - Add adversarial coverage for Windows-looking and POSIX paths, UNC
    authorities, encoded metadata characters, non-UTF-8 POSIX paths, URI
    hierarchy operations, and legacy serde round trips.
  • chore: preserve one more schema layer during large tool compaction (#27084)
    ## Summary
    
    Some customer MCP tools expose large input schemas that exceed Codex's
    compact schema budget even after description stripping. Today, the final
    compaction pass collapses complex schemas starting at depth 2, which can
    erase important shallow call structure such as small `anyOf` branches,
    required fields, and help-mode entry points. In one reported case, this
    degraded a tool schema into `query: any | any`, leaving the model
    without enough structure to discover the required help call.
    
    This change raises the deep-schema collapse boundary from depth 2 to
    depth 3. That preserves one additional layer of the tool contract while
    still collapsing deeper expensive subtrees to `{}` when a schema remains
    over budget.
    
    ## What Changed
    
    - Increased `MAX_COMPACT_TOOL_SCHEMA_DEPTH` from `2` to `3`.
    - Updated the schema compaction traversal test to assert the new
    collapse boundary.
    - The resulting compacted shape keeps useful shallow structure, for
    example:
      - top-level argument names
      - shallow `anyOf` branches
      - required object fields
      - nested property names one level deeper than before
    
    ## Validation
    
    - Ran `just test -p codex-tools`: 81 tests passed.
    - Ran a golden schema corpus comparison over 214 discovered tool input
    schemas under `golden_schemas/*/mcp_tools/*/input_schema.json`.
    - Depth 2 and depth 3 had identical percentile token counts across the
    corpus.
      - Both ended with `0 / 214` schemas over 1k tokens.
    - Both ended with `0 / 214` schemas over the 4,000-byte compact JSON
    budget.
    - Only one golden schema changed, increasing from 49 to 56 tokens, so
    this does not appear to introduce a meaningful corpus-wide regression.
    
    Corpus percentile results:
    
    | Percentile | Depth 2 | Depth 3 |
    |---|---:|---:|
    | p0 | 9 | 9 |
    | p10 | 31 | 31 |
    | p25 | 54 | 54 |
    | p50 | 81 | 81 |
    | p75 | 143 | 143 |
    | p90 | 290 | 290 |
    | p95 | 431 | 431 |
    | p99 | 600 | 600 |
    | max | 832 | 832 |
  • feat(doctor): report editor and pager environment (#27081)
    ## Background
    
    This was prompted by
    [#26858](https://github.com/openai/codex/issues/26858), where the
    attached doctor report did not include the editor selection and I had to
    [ask which editor was in
    use](https://github.com/openai/codex/issues/26858#issuecomment-4653829891)
    before investigating the external-editor newline issue. Capturing these
    variables in doctor makes that context available up front in future
    reports.
    
    `codex doctor` is intended to capture enough local context to diagnose
    startup and terminal behavior, but it did not report the environment
    variables that select an external editor or configure command pagers.
    
    The TUI [prefers `VISUAL` over
    `EDITOR`](https://github.com/openai/codex/blob/56554904babcaacf4444a2cc90716880837dff7c/codex-rs/tui/src/external_editor.rs#L31-L38),
    so missing or unexpected values can explain why the external-editor
    shortcut fails or launches the wrong command. Pager values are also
    useful inherited-shell context even though [unified exec normalizes its
    effective pager variables to
    `cat`](https://github.com/openai/codex/blob/56554904babcaacf4444a2cc90716880837dff7c/codex-rs/core/src/unified_exec/process_manager.rs#L60-L70).
    
    These variables can contain arbitrary command arguments or inline
    environment assignments. The human report is local, but `codex doctor
    --json` may be attached to feedback, so the machine-readable report
    should not include their raw contents.
    
    ## What Changed
    
    - Report `VISUAL` and `EDITOR` in the system environment details, using
    `not set` when either variable is absent.
    - Report inherited `PAGER`, `GIT_PAGER`, `GH_PAGER`, and `LESS` values
    when present.
    - Preserve full values in local human output while reducing these fields
    to `set` or `not set` in redacted JSON output.
    - Add structured check, JSON-redaction, rendered-output, and snapshot
    coverage.
    
    ## How to Test
    
    1. From `codex-rs`, run Codex with explicit editor and pager variables:
    
       ```sh
    env VISUAL='code --wait' EDITOR=vim PAGER='less -R' GIT_PAGER=delta
    GH_PAGER=less LESS=-FRX \
         cargo run -p codex-cli --bin codex -- doctor --no-color
       ```
    
    2. Confirm the `system` details show the full values for all six
    variables.
    3. Unset the pager variables and rerun the command. Confirm pager rows
    are omitted while missing editor variables are shown as `not set`.
    4. Run the same configured environment with `doctor --json`. Confirm
    each configured editor or pager field is reported as `set` and none of
    the raw commands or arguments appear in the JSON.
    
    Targeted tests:
    
    - `just test -p codex-cli` (279 tests passed)
  • [codex] Add OTEL counter descriptions (#26091)
    ## Why
    
    Metric descriptions should be declared with reusable OTEL instruments
    instead of being coupled to individual consumers. Counter descriptions
    are the smallest API primitive needed by the exec-server observability
    work.
    
    ## What changed
    
    - Adds `counter_with_description` while preserving the existing counter
    API.
    - Caches counters by name and description so instrument metadata remains
    part of the declaration identity.
    - Covers the exported description together with the existing value and
    attribute contract.
    
    This PR only adds counter descriptions. It does not add gauges,
    second-based durations, or exec-server adoption.
    
    ## Stack
    
    1. **#26091: counter descriptions**
    2. #27057: gauge instruments
    3. #27058: second-based duration histograms
    
    Related independent coverage: #27059 tests OTLP HTTP log and trace event
    export.
    
    The `codex-exec-server` bounded service tag now stays with the
    exec-server adoption change instead of this reusable infrastructure
    stack.
    
    ## Validation
    
    - `just test -p codex-otel`
    - `just fix -p codex-otel`
    - `just fmt`
  • Use cached remote plugin catalog for plugin list (#26932)
    ## Summary
    
    This changes the default remote plugin marketplace listing to use the
    cached global remote catalog when it is already present on disk. The
    foreground `plugin/list` response can then return from the local catalog
    cache instead of waiting on `/ps/plugins/list`.
    
    When a cached global catalog was present at the start of the request,
    `plugin/list` still schedules a background refresh through the existing
    plugin-list background task path so the disk cache is updated for future
    requests. Cache misses keep the existing synchronous remote fetch path
    and write the cache, and they do not schedule an extra duplicate
    background `/ps/plugins/list` refresh.
    
    Installed/enabled state continues to come from the existing remote
    installed overlay path. This change only affects the global remote
    catalog directory data used by `plugin/list`.
    
    ## Testing
    
    - `just fmt`
    - `just test -p codex-app-server
    plugin_list_uses_cached_global_remote_catalog_and_refreshes_it`
    - `just test -p codex-core-plugins`
    - `git diff --check`
  • [codex] Prune stale curated plugin caches (#26934)
    Curated plugin startup refresh now removes cached plugins whose names no
    longer appear in the raw openai-curated marketplace. This prevents users
    with the old standalone Google Sheets plugin selected locally from
    continuing to load its stale cache after the curated repo drops it.
    
    Existing config is left untouched, and plugins still present in the
    marketplace continue to refresh from local curated sources.
    
    Validation:
    - `just fmt`
    - `just test -p codex-core-plugins`
    - `git diff --check`
  • feat: support oneOf and allOf in tool input schemas (#24118)
    ## Why
    
    Some connector golden schemas use JSON Schema composition keywords
    beyond `anyOf`, specifically top-level or nested `oneOf` and `allOf`.
    Codex currently needs to preserve those shapes when parsing MCP tool
    input schemas so connector tools do not lose valid schema structure
    during normalization.
    
    To prevent an increased Responses API error rate, this PR will be merged
    after the Responses API supports top-level `oneOf`/`allOf`.
    
    ## What Changed
    
    - Adds `oneOf` and `allOf` support to `JsonSchema`, matching the
    existing `anyOf` handling.
    - Traverses `oneOf` and `allOf` anywhere schema children are visited,
    including sanitization, definition reachability, description stripping,
    and deep schema compaction.
    - Adds a final large-schema compaction pass that prunes schema objects
    containing `anyOf`, `oneOf`, or `allOf` to `{}` if earlier compaction
    passes still leave the schema over budget.
    
    ## Validation
    Golden schema token validation over `2,025` schemas under
    `golden_schemas`, all parsed successfully. Token count is `o200k_base`
    over compact JSON from `parse_tool_input_schema`.
    
    | Percentile | Before PR | After oneOf/allOf | After pruning |
    |---|---:|---:|---:|
    | p0 | 9 | 9 | 9 |
    | p10 | 63 | 64 | 64 |
    | p25 | 86 | 87 | 87 |
    | p50 | 125 | 128 | 128 |
    | p75 | 203 | 206 | 206 |
    | p90 | 327 | 333 | 333 |
    | p95 | 460 | 473 | 473 |
    | p99 | 763 | 779 | 779 |
    | max | 891 | 955 | 955 |
    
    Totals:
    
    | Parser state | Total tokens |
    |---|---:|
    | Before PR | 345,713 |
    | After oneOf/allOf | 352,686 |
    | After pruning | 352,686 |
    
    The pruning column matches the oneOf/allOf column for this corpus
    because no parsed compact golden schema remains over the `4,000`
    compact-byte budget after the earlier compaction passes.
  • [codex] Require complete main-agent skill reads (#27044)
    ## Summary
    - require the main agent to read selected `SKILL.md` files completely,
    continuing truncated or paginated reads through EOF
    - require the main agent to personally read task-required instruction
    references instead of delegating their interpretation
    - clarify that progressive disclosure selects relevant files without
    permitting partial reads
    - preserve subagent use for task work when the selected skill allows it
    - cover both absolute-path and aliased-root prompt variants
    
    ## Why
    Partial reads can skip routing and verification requirements later in
    skill instructions. Delegated summaries can also omit constraints the
    main agent needs to follow. The existing "Read only enough" wording made
    both behaviors appear acceptable.
    
    ## Impact
    Agents should follow complete selected skill instructions while
    continuing to avoid unrelated references, scripts, and assets. Subagents
    remain available for task execution where permitted.
    
    ## Test plan
    - `just test -p codex-core-skills` (101 passed)
    - `just fmt`
    - `git diff --check`
  • [codex-analytics] stop sending codex error subreason (#27060)
    ## Summary
    - stop emitting `codex_error_subreason` on `codex_turn_event`
    - remove the transient analytics fact plumbing that copied
    `CodexErr::InvalidRequest(String)` into the event
    - update analytics serialization coverage accordingly
    
    ## Why
    `codex_error_subreason` is a free-form copy of `InvalidRequest(String)`,
    including raw provider 400 bodies in some paths. That makes it unsafe as
    an analytics field because it can carry user-derived or sensitive text.
    
    ## Validation
    - `just fmt`
    - `just test -p codex-analytics`
  • Route image edits through referenced file paths (#26486)
    ## Why
    
    Image edits should use the exact images selected by the model instead of
    inferring edit inputs from conversation history.
    
    ## What changed
    
    - Replaced the image tool's `action` argument with optional
    `referenced_image_paths`.
    - Treats omitted or empty references as generation and populated
    references as editing.
    - Reads referenced absolute image paths and packages them as image data
    URLs for the edit request.
    - Removed the previous history-selection and image-count heuristics.
    - Updated direct and code-mode tool instructions and calls.
    - Added an app-server integration test covering an attached image routed
    to the image edit endpoint.
    
    ## Validation
    - Tested end-to-end on local `just codex` with copy pasted image,
    attached image, etc.
    - `just test -p codex-image-generation-extension`
    - `just test -p codex-app-server
    standalone_image_edit_uses_attached_model_visible_image`
    - `just fix -p codex-image-generation-extension`
    - `just bazel-lock-check`
  • Enforce configured network proxy in codex sandbox (#27035)
    ## Why
    
    `codex sandbox` can start a network proxy from a configured permission
    profile. Previously, sandbox-level containment was tied to managed
    network requirements rather than whether a proxy was actually active.
    This meant config-driven proxy policies were not consistently enforced
    as the sandbox's only network path.
    
    ## What changed
    
    - Enable proxy-only network containment whenever `codex sandbox` starts
    a network proxy.
    - Apply the same active-proxy check to the macOS and Linux sandbox
    paths.
    - Add a Linux regression test that verifies a sandboxed command cannot
    establish a direct connection while the configured proxy is active.
    
    ## Test plan
    
    - `just test -p codex-cli debug_sandbox::tests`
    - `sandbox_with_network_proxy_blocks_direct_loopback_access` runs on
    Linux to cover the config-driven proxy path end to end.
  • cli: add -P sandbox permissions profile alias (#27054)
    ## Why
    
    `codex sandbox --permissions-profile` is useful when running commands
    under a named permissions profile, but the long option is cumbersome for
    a debugging-oriented command. `-p` is already used for the config
    profile selector, so `-P` gives the permissions profile selector a
    compact, non-conflicting alias.
    
    ## What Changed
    
    - Added `short = 'P'` to the `permissions_profile` option for the macOS,
    Linux, and Windows sandbox command structs in
    [`codex-rs/cli/src/lib.rs`](https://github.com/openai/codex/blob/6d9f9c5cdcaa0a156aa2dabbde259ae5e9e8bc0b/codex-rs/cli/src/lib.rs#L29-L112).
    - Added parser coverage for `codex sandbox -P :workspace -- echo` in
    [`codex-rs/cli/src/main.rs`](https://github.com/openai/codex/blob/6d9f9c5cdcaa0a156aa2dabbde259ae5e9e8bc0b/codex-rs/cli/src/main.rs#L2883-L2896).
    
    ## Verification
    
    - `just test -p codex-cli` passed, including the new
    `sandbox_parses_permissions_profile_short_alias` parser test.
  • 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.
  • [codex] Calm multi-agent v2 usage prompts (#27037)
    ## Summary
    - tighten the default multi-agent v2 root and subagent usage hints to
    bias toward local work
    - add a pre-call gate to the v2 spawn_agent description for independent,
    bounded, parallelizable subtasks
    
    ## Validation
    - just fmt
    - started just test -p codex-core, but it was interrupted before
    completion per follow-up request to commit and push immediately
  • [codex] Clarify PR babysitter state mutations (#27038)
    # Why
    
    Codex is doing a bit too much on my PRs that it's babysitting. In
    particular I'd like it to not interact with comment threads that involve
    other humans -- I should be the one doing human interaction. This is
    tricky because it's still very useful to be able to drop review comments
    myself and have Codex iterate on them.
    
    ## What
    
    This updates `.codex/skills/babysit-pr/SKILL.md` with an explicit GitHub
    state mutation policy.
  • fix: preserve auto review across config and delegation (#26230)
    ## Why
    
    Auto Review should remain the effective approval reviewer when settings
    cross runtime boundaries. A config or app-server round trip must not
    change the reviewer identity, and delegated work must not silently fall
    back to user review.
    
    This requires both a stable canonical serialized value and propagation
    of the effective setting. `auto_review` is the canonical value across
    protocol and app-server output, while `guardian_subagent` remains
    accepted as backward-compatible input.
    
    ## What changed
    
    - serialize `ApprovalsReviewer::AutoReview` consistently as
    `auto_review` across core protocol and app-server v2
    - continue accepting `guardian_subagent` when reading existing config or
    client requests
    - carry the active turn's approval reviewer into spawned agents
    - update config/debug expectations and add delegated-task regression
    coverage
    
    ## Scope
    
    This does not change Guardian policy or remove compatibility with
    existing `guardian_subagent` inputs. It preserves the selected reviewer
    across serialization, config reloads, app-server settings, and delegated
    task setup.
    
    Related Guardian changes are split independently:
    
    - #26231 adds denials and soft denials
    - #26334 retries transient reviewer failures
    - #26333 reuses narrowly scoped low-risk approvals
    - #26232 adds TUI denial recovery
    
    ## Validation
    
    - `just test -p codex-app-server-protocol` (224 passed)
    - regression coverage for delegated task reviewer propagation
    - serialization coverage for canonical `auto_review` output and legacy
    `guardian_subagent` input
    
    ---------
    
    Co-authored-by: saud-oai <saud@openai.com>
  • ci: template custom runner names by repo (#27024)
    ## Why
    
    These workflows currently hard-code the `codex` runner group and custom
    runner labels. That makes the same workflow definitions less portable
    across repository copies or renamed repos, even though the runner fleet
    follows the repository name scheme. Template the runner identities from
    the repository name so `openai/codex` still resolves to the existing
    `codex-*` runners while other repos can use their own `<repo>-*` runner
    names.
    
    ## What Changed
    
    - Replaced custom runner `group` values such as `codex-runners` with
    `${{ github.event.repository.name }}-runners`.
    - Replaced custom runner labels such as `codex-linux-x64` and
    `codex-windows-arm64` with `${{ github.event.repository.name }}-...`.
    - Covered direct `runs-on` objects, matrix `runs_on` entries, reusable
    workflow runner inputs, and release runner labels.
    
    ## Verification
    
    - Parsed all `.github/workflows/*.yml` files as YAML with Ruby.
    - Searched `.github/workflows` to confirm no hardcoded runner-field
    `codex-runners` or `codex-*` labels remain.
  • [plugins] Expose marketplace source in marketplace list JSON (#27009)
    ## Summary
    - Follow-up to #26417 and #26631
    - Add `marketplaceSource` to `codex plugin marketplace list --json`
    entries for configured marketplaces
    - Reuse the existing `marketplaceSource` shape from `codex plugin list
    --json`
    - Keep human-readable marketplace list output unchanged
    - Add CLI coverage for configured local and git marketplace sources
    
    Example:
    
    ```json
    {
      "marketplaces": [
        {
          "name": "debug",
          "root": "/path/to/.codex/.tmp/marketplaces/debug",
          "marketplaceSource": {
            "sourceType": "git",
            "source": "https://example.com/acme/agent-skills.git"
          }
        }
      ]
    }
    ```
    
    ## Validation
    - `just fmt`
    - `just fix -p codex-cli`
    - `just test -p codex-cli marketplace_list`
    - `just test -p codex-cli`
  • [codex] Speed up external agent session imports (#26637)
    ## Why
    
    Importing large external-agent session histories currently starts a full
    live Codex thread for every imported session. This initializes unrelated
    runtime systems and repeats expensive transcript, metadata, hashing, and
    ledger work.
    
    On a 50-session, 238 MiB fixture, the existing path took roughly 70
    seconds to complete the import and 77 seconds end to end.
    
    ## What changed
    
    - Persist imported sessions directly through `ThreadStore` instead of
    starting full live threads.
    - Process imports through a bounded five-session pipeline.
    - Parse, extract, and hash each source file in one pass.
    - Move blocking source preparation onto the blocking thread pool.
    - Reuse prepared content hashes and update the import ledger once per
    batch.
    - Avoid metadata readback for newly written rollouts.
    - Preserve imported conversation history and visible thread metadata.
    - Keep the implementation out of `codex-core` and avoid changes to the
    public `ThreadStore` trait.
    
    ## Performance
    
    For the same 50-session, 238 MiB fixture:
    
    | Path | Import completion | End to end |
    | --- | ---: | ---: |
    | Existing import | 69.61s | 76.62s |
    | This change | 5.95s | 6.58s |
    
    All 50 sessions imported successfully with no warnings or contention
    signals.
    
    ## Validation
    
    - `just test -p codex-external-agent-sessions`
    - `just test -p codex-app-server external_agent_config_import`
    - Verified imports do not initialize unrelated required MCP servers.
    - Verified previously imported source versions are skipped and changed
    sources can be imported again.
    - Verified imported rollouts remain readable through thread listing and
    history APIs.
  • [codex-analytics] report compaction analytics details (#26680)
    ## Why
    
    Compaction analytics adds retained image count and compaction summary
    output tokens for v1.5 specifically.
    
    ## What changed
    
    - Add nullable `retained_image_count` and `compaction_summary_tokens`
    fields to `codex_compaction_event`.
    - Populate them only for `responses_compaction_v2`: retained images come
    from the retained v2 compacted history, and summary tokens come from
    `response.completed.token_usage.output_tokens`.
    - Leave local and legacy remote compaction events as `null` for these
    detail fields.
    
    ## Verification
    
    - `just fmt`
    - `just fix -p codex-core`
    - `just test -p codex-core
    build_v2_compacted_history_counts_retained_input_images`
    - `git diff --check`
  • 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.