Commit Graph

7244 Commits

  • 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
    `&`.
    
    ## 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.
  • fix(app-server): avoid blocking connection cleanup (#26852)
    ## Why
    
    Remote-control app-server sessions can reconnect every 5-7 seconds when
    the shared transport-event queue fills. The queue's consumer handled
    `ConnectionClosed` by awaiting all in-flight RPCs for the disconnected
    connection. A stuck RPC therefore blocked processing of replacement
    connection and initialize events until remote-control forwarding hit its
    five-second timeout and reconnected again.
    
    Related issue: N/A (internal remote-control incident investigation).
    
    ## What Changed
    
    - Split fast RPC admission closure from draining:
    `ConnectionRpcGate::close()` rejects queued and future RPCs, while
    `shutdown()` continues waiting for RPCs that already started.
    - Close a disconnected connection's RPC gate before spawning the
    existing RPC drain and resource cleanup in a tracked background task, so
    the transport-event consumer remains available without waiting for
    active RPCs.
    - Reap completed cleanup tasks during normal operation, drain them
    during graceful shutdown, and abort them during forced shutdown.
    - Add regression coverage for closing with an active RPC, rejecting
    post-close requests without polling them, and preserving the existing
    shutdown wait behavior.
    
    ## Verification
    
    `just test -p codex-app-server --lib connection_rpc_gate` passes all 6
    tests, including the new close-versus-drain regression coverage.
  • [codex] Restore release symbol artifacts with line tables (#26202)
    ## Summary
    
    - Restore separate release symbol archives for macOS, Linux, and Windows
    binaries.
    - Build release binaries with `line-tables-only` debuginfo instead of
    full debuginfo.
    - Strip Unix distribution binaries after extracting symbols, preserve
    Windows PDBs, and keep symbol archives available to the release job.
    - Strip the packaged Linux `bwrap` binary before hashing it so the
    embedded digest matches the distributed bytes.
    
    ## Root cause
    
    The first symbol-artifact implementation enabled
    `CARGO_PROFILE_RELEASE_DEBUG=full`. In the June 2 release runs, macOS
    ARM primary builds reached the 90-minute timeout while still inside
    `Cargo build`. After the symbol changes were reverted, the same primary
    build completed in about 22 minutes. The archive step itself completed
    in tens of seconds when reached.
    
    Rust's `line-tables-only` debuginfo level preserves function names and
    source locations for symbolication without emitting the heavier variable
    and type information from full debuginfo.
    
    ## Validation
    
    - Ran `just fmt` from `codex-rs`.
    - Ran `just test-github-scripts` from the repository root: 23 tests
    passed.
    - Ran `bash -n` and `shellcheck` on
    `.github/scripts/archive-release-symbols-and-strip-binaries.sh`.
    - Parsed both modified workflows as YAML and ran `git diff --check`.
    - Built a macOS release smoke binary with `line-tables-only`, archived
    its dSYM through the restored script, stripped the production binary,
    and verified that `atos` resolves `symbol_smoke_function` to
    `main.rs:2`.
    - Ran Linux archive-script control-flow coverage with stubbed `objcopy`
    and `strip` commands.
    - Ran Windows PDB archive staging coverage and verified
    underscore-emitted Rust PDB names are staged under shipped hyphenated
    binary names.
    
    ## Follow-up
    
    The release workflow only runs for tags or manual dispatches, so CI
    cannot dry-run the full release matrix on this PR. The next release run
    will verify runner time and memory behavior under `line-tables-only`.
  • [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
  • Avoid reopening v2 descendants on resume (#26997)
    ## Why
    
    Multi-agent v2 residency is intended to keep only the threads that need
    to be live. The existing rollout resume path still walked persisted open
    descendants and reopened the entire descendant tree when resuming a v2
    root, which turns resume into an eager reload of work that should stay
    unloaded until it is explicitly needed.
    
    The interrupted-agent path has a related residency issue. Interrupted
    agents remain open by design, so an idle interrupted resident should be
    eligible for eviction just like an idle completed or errored resident.
    Otherwise a resident set full of interrupted agents can consume every v2
    slot and block later spawns or reloads with `AgentLimitReached`.
    
    ## What Changed
    
    - Return early from `resume_agent_from_rollout` after resuming a v2
    thread so persisted v2 descendants are not reopened eagerly.
    - Treat idle `Interrupted` v2 residents as unloadable in the LRU
    residency path.
    - Add focused coverage for v2 root resume leaving descendants unloaded
    and for eviction of an idle interrupted v2 resident when a new slot is
    needed.
    
    ## Verification
    
    Added targeted `codex-core` tests covering:
    
    - v2 root resume with persisted descendants, verifying only the root is
    loaded after resume.
    - residency eviction of an idle interrupted v2 agent when the resident
    set is full.
  • Rename multi-agent v2 close_agent to interrupt_agent (#26994)
    ## Why
    
    `close_agent` is the wrong model-facing name for the v2 operation after
    the residency changes. V2 agents remain reusable by task name, and
    residency/unloading owns capacity management; the exposed tool should
    describe the action it actually performs: interrupt the target agent's
    current turn without making the agent unavailable for future messages or
    follow-up tasks.
    
    ## What changed
    
    - Rename the multi-agent v2 tool from `close_agent` to
    `interrupt_agent`.
    - Keep the v1 `close_agent` surface unchanged.
    - Update the v2 handler to send `Op::Interrupt`, keep interrupted agents
    registered, and reject root/self targets with interrupt-specific errors.
    - Route interrupt delivery through the existing dead-thread cleanup path
    so stale resident entries do not keep consuming capacity.
    - Update tool planning and handler tests for the new v2 surface and
    semantics.
    
    ## Verification
    
    Added focused coverage in:
    
    - `core/src/tools/spec_plan_tests.rs`
    - `core/src/tools/handlers/multi_agents_tests.rs`
  • 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`
  • Ignore proc-macro-error2 advisory (#26974)
    ## Summary
    - ignore RUSTSEC-2026-0173 in cargo-deny and cargo-audit config
    - document that proc-macro-error2 is pulled in transitively via
    i18n-embed-fl/age/codex-secrets
    - leave the ignore temporary until codex-secrets moves off age or age
    drops i18n-embed-fl
    
    ## Validation
    - just fmt
    - cargo deny check --hide-inclusion-graph
  • feat: add v2 agent residency lru (#26632)
    ## Why
    
    Multi-agent v2 treats agents as durable logical agents, not just live
    entries in `ThreadManager`. After the reload-on-delivery change, a v2
    agent can be addressed even if its thread is not currently loaded.
    
    This PR adds the next layer: loaded v2 subagents can be paged out of
    `ThreadManager` when the session has too many resident agents. That
    keeps residency separate from logical identity and prepares the stack
    for making v2 concurrency count active execution instead of existing
    agents.
    
    ## What Changed
    
    - Add an `AgentControl`-scoped LRU for resident v2 subagents.
    - Reserve residency before spawning or reloading a v2 subagent.
    - If resident capacity is full, unload the least-recently-used idle v2
    subagent from `ThreadManager`.
    - Keep `ThreadManager` as a primitive loaded-thread store; it does not
    own the LRU policy.
    - Keep unloaded agents registered and durable so they can be reloaded by
    the delivery path.
    - Preserve the existing v2 cap semantics by using the derived non-root
    v2 cap for residency.
    
    Eviction is intentionally conservative. A thread is unloadable only when
    it is a v2 subagent, has completed or errored, has no active turn, and
    has no pending mailbox work. Before removal, the rollout is materialized
    and flushed.
    
    ## Assumptions And Non-Goals
    
    - PR #26623 provides the reload-on-delivery path for unloaded v2 agents.
    - `ThreadManager` membership means loaded/resident, not logical agent
    existence.
    - `AgentRegistry` remains the logical identity/metadata source for v2
    agents that may be unloaded.
    - `list_agents` remains a recent/resident view for now.
    - This does not change active execution concurrency; that is the next
    PR.
    - This does not change `close_agent` semantics.
    - This does not change or remove `resume_agent`.
    - This does not add a new residency config knob.
    
    ## Stack
    
    1. V2 durable lookup and reload on delivery (#26623) - reload unloaded
    v2 agents before delivering follow-up/input.
    2. V2 residency LRU (this PR) - unload idle resident v2 agents from
    `ThreadManager` when resident capacity is full.
    3. V2 active-execution concurrency - count running non-root v2 turns
    instead of logical agents.
    4. V2 close/interrupt semantics - make v2 close interrupt the current
    turn without deleting durable identity.
    5. V2 resume cleanup - remove the manual resume surface for v2 while
    keeping internal reload support.
    
    ## Validation
    
    - Added focused coverage for the residency LRU eviction path.
    - Local clippy/check/tests were not run; CI will cover them.
  • [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(tui): scope MCP startup status by thread (#26639)
    ## Why
    
    MCP startup failures from spawned subagents were rendered as global
    notifications, so a child thread's failure could pollute the visible
    parent transcript. Routing the notification to the child exposed two
    related replay problems: session refresh could discard the buffered
    event, and a newly created child `ChatWidget` did not know the expected
    MCP server set, which could leave its startup spinner running after
    every server had settled.
    
    MCP startup diagnostics should remain visible in the thread that owns
    the startup without affecting other transcripts. The protocol also needs
    to support a future app-scoped MCP lifecycle where startup is not owned
    by any thread.
    
    ## Reported Behavior
    
    The [originating Slack
    report](https://openai.slack.com/archives/C08JZTV654K/p1780604538859939)
    called out that using subagents could turn MCP startup failures into a
    wall of yellow CLI warnings because repeated failures were not
    deduplicated. The intended behavior is for those diagnostics to remain
    visible once in the thread that owns the startup, without polluting the
    parent transcript.
    
    ## What Changed
    
    - add nullable `threadId` ownership to `mcpServer/startupStatus/updated`
    - populate it from the app-server conversation ID for the current
    thread-scoped lifecycle and regenerate the protocol schema and
    TypeScript artifacts
    - treat a missing or null `threadId` as app-scoped without injecting it
    into the active chat transcript
    - route and buffer thread-owned MCP startup notifications by thread in
    the TUI
    - preserve buffered MCP startup events across child session refresh
    - seed expected MCP servers before replaying a thread snapshot so
    startup reaches its terminal state
    - suppress an identical repeated failure warning for the same server
    within one startup round
    
    The owning thread still renders the detailed failure and final `MCP
    startup incomplete (...)` summary.
    
    ## How to Test
    
    1. Configure an optional MCP server named `smoke` that exits during
    initialization.
    2. Launch the TUI with multi-agent support enabled.
    3. Confirm the main thread's own startup failure renders one detailed
    `smoke` warning and one incomplete-startup summary.
    4. Spawn exactly one subagent.
    5. Confirm the parent transcript does not receive the subagent's MCP
    startup failure.
    6. Switch to the subagent thread and confirm it contains exactly one
    detailed `smoke` failure and one incomplete-startup summary.
    7. Confirm the subagent's MCP startup spinner disappears and the thread
    remains usable.
    8. Switch between the parent and subagent and confirm the warnings
    neither move nor duplicate.
    
    Targeted tests:
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    thread_start_emits_mcp_server_status_updated_notifications`
    - `just test -p codex-tui mcp_startup`
    
    The parent/child behavior and spinner completion were also exercised
    manually in tmux. `just argument-comment-lint` was attempted but blocked
    by an unrelated local Bazel LLVM empty-glob failure; touched Rust
    callsites were inspected manually.
  • fix(tui): accept prompts with resume and fork (#26818)
    ## Why
    
    Interactive `codex resume` and `codex fork` expose both a session ID
    positional and an initial prompt positional. With `--last`, Clap still
    assigns the first positional to the session ID, so a command such as
    `codex fork --last "/compact focus on auth"` either fails parsing or
    attempts to look up the prompt as a session ID instead of sending it to
    the latest session.
    
    This makes it impossible to select the latest session and immediately
    provide a follow-up prompt, even though `codex exec resume --last`
    already supports that workflow.
    
    <img width="1746" height="1024" alt="CleanShot 2026-06-06 at 17 00
    47@2x"
    src="https://github.com/user-attachments/assets/86885c07-a23c-48ee-b0ee-47f2484f6eb7"
    />
    
    ## What Changed
    
    - Reinterpret the first positional as the initial prompt when
    interactive `resume --last` or `fork --last` is used and no explicit
    second prompt was parsed.
    - Preserve the existing `resume SESSION_ID PROMPT` and `fork SESSION_ID
    PROMPT` behavior.
    - Add parser-level regression coverage for latest-session and
    explicit-session prompt forms.
    
    ## How to Test
    
    1. Start an interactive session, exit it, then run `codex resume --last
    "continue from the latest session"`.
    2. Confirm Codex resumes the latest session and submits the supplied
    prompt instead of treating it as a session ID.
    3. Run `codex fork --last "take a different approach"`.
    4. Confirm Codex forks the latest session and submits the supplied
    prompt.
    5. Also verify `codex resume SESSION_ID "continue here"` and `codex fork
    SESSION_ID "branch here"` still target the explicit session and submit
    the prompt.
    
    Targeted tests:
    - `just test -p codex-cli` (267 passed)
  • 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
  • ci: use bazel environment for BuildBuddy secret (#26895)
    ## Why
    
    `BUILDBUDDY_API_KEY` now lives in the `bazel` GitHub Actions environment
    as an environment secret. Jobs that need BuildBuddy credentials must opt
    into that environment so `${{ secrets.BUILDBUDDY_API_KEY }}` resolves
    from the protected environment secret instead of relying on an unscoped
    repository/organization secret.
    
    This follows the same environment-secret migration pattern as #26466.
    
    ## What Changed
    
    - Attach each workflow job that reads `BUILDBUDDY_API_KEY` to the
    `bazel` environment.
    - Set `deployment: false` on those job-level environment blocks.
    
    `deployment: false` lets the job enter the `bazel` environment to access
    its environment secrets without creating GitHub deployment records for
    these CI jobs. That keeps the environment as a secret/access-control
    boundary without making ordinary Bazel CI runs look like deploys.
    
    ## Validation
    
    - Parsed the modified workflow YAML files with Ruby's YAML parser.
    - Checked the modified workflow files for trailing whitespace.
  • fix(remote-control): preserve enrollment on generic websocket 404s (#26741)
    ## Why
    
    A remote-control WebSocket handshake can receive a generic HTTP 404 when
    an intermediary routes the request without preserving the WebSocket
    upgrade. Treating every 404 as proof that the remote app server is gone
    clears valid enrollment and causes repeated re-enrollment, new
    environment and server IDs, Habitat churn, and noisy `/server/enroll`
    traffic.
    
    ## What Changed
    
    - Clear enrollment only when a 404 JSON response explicitly contains
    `{"detail":"Remote app server not found"}`.
    - Preserve enrollment for empty, plain-text, malformed, or otherwise
    unrecognized 404 responses, return the transport error, and retry with
    the existing reconnect backoff.
    - Log the status, correlation headers (`request-id` or
    `x-oai-request-id`, plus `cf-ray`), and bounded/redacted response body
    for unrecognized 404s.
    - Cover both explicit missing-server re-enrollment and generic 404
    enrollment preservation/reconnect behavior.
    
    ## Verification
    
    `just test -p codex-app-server-transport` passes all 114 tests on the
    rebased branch, including the targeted explicit and generic WebSocket
    404 scenarios.
    
    Related issue: N/A
  • Remove just bench-smoke from just test. (#26716)
    ## Why
    
    `just test` should run the test suite without also compiling and
    executing benchmark smoke tests. Keeping benchmark validation explicit
    avoids adding unrelated work to every project-specific test invocation.
    
    ## What changed
    
    - Remove the `just bench-smoke` step from the Unix and Windows `test`
    recipes.
    - Document `just bench` and `just bench-smoke` as the explicit benchmark
    commands in `AGENTS.md`.
    
    ## Validation
    
    - `just test -p codex-arg0`
    - `just --dry-run test`
    - `just --dry-run bench-smoke`
  • [codex] Deduplicate skill load warnings (#26698)
    Skill reloads can get noisy when the watcher keeps triggering
    `skills/list` and the same invalid `SKILL.md` error comes back each
    time.
    
    This keeps the first warning visible, then suppresses repeats while the
    same `(path, message)` is still active. If the error clears and later
    comes back, or if the message changes, it will show again.
    
    Validation:
    - `just fmt`
    - `just test -p codex-tui skill_load_warning_state`
  • permissions: enforce managed permission profile allowlists (#24852)
    ## Why
    
    Permission profile allowlists are an enterprise security boundary, but
    they also need to compose across the managed requirements layers added
    in #24620.
    
    A map representation lets each requirements layer add, allow, or revoke
    individual profiles without replacing an entire array.
    
    ## Managed Contract
    
    Administrators configure the mergeable allow map with
    `allowed_permission_profiles`. A recommended enterprise configuration
    explicitly lists every built-in and custom profile users should be able
    to select:
    
    ```toml
    default_permissions = "review_only"
    
    [allowed_permission_profiles]
    ":read-only" = true
    ":workspace" = true
    review_only = true
    # ":danger-full-access" is intentionally omitted, so it is denied.
    
    [permissions.review_only]
    extends = ":read-only"
    ```
    
    - Profiles whose effective merged value is `true` are allowed.
    - Missing profiles and profiles set to `false` are denied.
    - This is a closed allowlist: built-in profiles and profiles introduced
    in future versions are denied unless explicitly allowed.
    - Explicitly list each built-in profile the enterprise wants to make
    available. Omit built-ins such as `:danger-full-access` when they should
    remain unavailable.
    - Set `default_permissions` explicitly to the allowed profile users
    should receive when they have no local selection.
    - Higher-precedence layers override only the profile keys they define.
    - `false` is only needed when a higher-precedence layer must revoke a
    `true` inherited from a lower layer.
    - Explicit keys must refer to known built-in or managed profiles.
    
    A custom or narrowed allowlist requires an allowed
    `default_permissions`. For compatibility, if both `:workspace` and
    `:read-only` are explicitly allowed, an omitted default resolves to
    `:workspace`; customer configurations should still set the intended
    default explicitly.
    
    When `allowed_permission_profiles` is absent, existing implicit
    permission and legacy `sandbox_mode` behavior is unchanged.
    
    ## What Changed
    
    - Add `allowed_permission_profiles` as a `BTreeMap<String, bool>` that
    merges per profile across requirements layers.
    - Enforce managed defaults, strict denial of omitted profiles, and the
    explicitly allowed standard-pair fallback.
    - Expose `allowedPermissionProfiles` through `configRequirements/read`
    and regenerate its schemas.
    - Add regression coverage for map composition and revocation, managed
    defaults, strict denial of omitted built-ins, and API output.
    
    ## Verification
    
    - Focused `codex-config` coverage for layered map composition and
    revocation
    - Focused `codex-core` coverage for managed defaults, invalid defaults,
    strict denial of omitted built-ins, and the standard built-in pair
    - Focused `codex-app-server` coverage for requirements API output
    - Scoped Clippy for `codex-config`, `codex-core`,
    `codex-app-server-protocol`, and `codex-app-server`
    
    ## Documentation
    
    The managed `requirements.toml` documentation should introduce
    `allowed_permission_profiles` as a closed permission-profile allowlist
    before this setting is published on developers.openai.com.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [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] Gate terminal visualization instructions in TUI (#26013)
    ## Summary
    - add `Feature::TerminalVisualizationInstructions` as
    `UnderDevelopment`, disabled by default
    - keep terminal visualization instructions inside the TUI package
    - append them to existing developer instructions for TUI start, resume,
    and fork flows only when enabled
    - intentionally do not apply them to `codex exec`
    
    ## Rollout
    Control behavior is unchanged. TUI dogfooders can enable
    `terminal_visualization_instructions`; no default user receives the new
    terminal-specific instructions.
    
    The shared visualization-selection rule is supplied separately through
    the `codex_proxy_model_3` Statsig layer for every target Codex model
    slug in the gated cohort. This TUI feature determines how to render an
    appropriate visualization on the terminal surface; the model-layer
    treatment determines when to use one.
    
    ## Validation
    - `cargo test -p codex-tui
    terminal_visualization_instructions_are_gated_for_all_tui_thread_flows
    --lib`
    - `cargo test -p codex-features --lib`
    - `cargo fmt --all -- --check`
    - `git diff --check`
    - GPT-5.4 and GPT-5.5 real prompt-pipeline smoke tests: both visualized
    the positive mapping case, abstained on the negative route case, and
    passed exact prompt-stack verification on CLI and App
    - refreshed onto current `main` with a clean merge and reran the focused
    validation
    
    The full 53-probe all-model treatment comparison and requested
    production coding evals remain rollout gates before broadening beyond
    the initial employee cohort.
    
    This PR remains open for normal human review.
  • [codex] Use standalone tools for Responses Lite (#26490)
    ## Summary
    
    Responses Lite does not execute hosted Responses tools, so models using
    it must route web search and image generation through Codex-owned
    executors & standalone Response's API endpoints.
    
    This PR is stacked on #26487.
    
    ## Validation
    
    - `cargo test -p codex-core responses_lite_ --lib`
    - `cargo test -p codex-core
    standalone_executors_remain_hidden_without_flags_or_responses_lite
    --lib`
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates --lib`
    - `cargo test -p codex-web-search-extension -p
    codex-image-generation-extension`
    - `cargo test -p codex-app-server --test all standalone_`
    - `cargo fmt --all -- --check`
  • [codex] Remove legacy remote plugin startup sync (#25936)
    ## Summary
    
    - Remove the legacy startup remote plugin sync path that called
    `/plugins/list` and reconciled curated plugin cache/config.
    - Remove the `sync_plugins_from_remote` API, its result/error types,
    startup marker task, and tests that expected the legacy request.
    - Keep the current remote installed bundle sync and remote catalog flows
    (`/ps/plugins/installed` and `/ps/plugins/list`) intact.
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `env HOME=/private/tmp/codex-xin-build-home
    USERPROFILE=/private/tmp/codex-xin-build-home just test -p
    codex-core-plugins`
    - Searched for legacy `/plugins/list` sync references; remaining matches
    are `/ps/plugins/list` catalog tests/code.
    
    ## Notes
    
    - `just test -p codex-app-server plugin_list` is currently blocked
    before running filtered tests by an unrelated compile error in
    `app-server/tests/suite/v2/image_generation.rs`:
    `app_test_support::McpProcess` is not exported.