Commit Graph

6007 Commits

  • Treat refresh_token_reused 400s as relogin-required (#24830)
    ## Summary
    - classify known refresh-token terminal failures from `/oauth/token` as
    permanent even when the backend returns `400`
    - preserve the existing relogin-required message for
    `refresh_token_reused` instead of retrying and collapsing into a generic
    cloud requirements error
    - add regression coverage for `400 refresh_token_reused`
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-login`
  • [codex] Remove redundant SQLite dynamic tool storage (#24819)
    ## Why
    
    Dynamic tools are defined at thread start and already stored in rollout
    `SessionMeta`, which restores resumed and forked sessions. Persisting
    the same tools through SQLite creates a second runtime persistence path
    that is unnecessary prework for the explicit namespace refactor.
    
    ## What changed
    
    - Restore missing thread-start dynamic tools directly from rollout
    history, including when SQLite is enabled.
    - Remove SQLite dynamic-tool reads, writes, backfill, and thread
    metadata patch plumbing.
    - Add SQLite-enabled resume integration coverage that verifies a
    rollout-defined dynamic tool is still sent after resume.
    
    ## Compatibility
    
    The existing `thread_dynamic_tools` table is intentionally not dropped
    even though it's now unused. Older Codex binaries are allowed to open
    databases migrated by newer binaries and still reference this table;
    dropping it would break that mixed-version path. See
    [here](https://github.com/openai/codex/blob/main/codex-rs/state/src/migrations.rs#L10-L11).
    
    ## Verification
    
    - `just test -p codex-state -p codex-rollout -p codex-thread-store`
    - `just test -p codex-core --test all
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
  • [codex] Fix hyperlink-aware key-value table rendering (#24825)
    ## Why
    
    The key/value markdown table renderer added in #24636 still operates on
    `Line` values, while table cells and rendered table output now carry
    `HyperlinkLine`. That mismatch breaks `codex-tui` compilation on `main`
    and would risk losing semantic web-link annotations if corrected by
    flattening the values.
    
    ## What changed
    
    - Make key/value record rendering wrap and emit `HyperlinkLine` values
    consistently with the existing grid renderer.
    - Remap wrapped hyperlink ranges and shift them when value content is
    prefixed by record-mode indentation or labels.
    - Add focused coverage verifying key/value fallback output preserves
    web-link destinations.
    
    ## Verification
    
    - `just test -p codex-tui -E
    'test(key_value_table_keeps_web_annotations) |
    test(/table_renders_(key_value_records_when_compact_fragmentation_is_systemic_snapshot|stacked_key_value_records_when_path_column_becomes_too_narrow_snapshot|records_when_multiple_prose_columns_are_starved_snapshot)/)'`
  • Update rmcp to 1.7.0 (#24763)
    WIll make it easier to uprev when the new draft spec is supported.
    
    Also updates reqwest where needed for compatibility but doesn't update
    it everywhere since this is already a large diff.
    
    The new version of rmcp handles certain kinds of authentication failures
    differently, this patch includes support for identifying the failing scope
    in a WWW-Authenticate header.
  • Allow API-key auth for remote exec-server registration (#24666)
    ## Overview
    Allow remote `codex exec-server` registration to use existing API-key
    auth while restricting where those credentials can be sent.
    
    - Accept `CodexAuth::ApiKey` for the normal `--remote` registration
    path.
    - Restrict API-key remote registration to HTTPS `openai.com` and
    `openai.org` hosts and subdomains, with explicit HTTP loopback support
    for local development.
    - Disable registry registration redirects so credentials cannot be
    forwarded to an unvalidated destination.
    - Retain `--use-agent-identity-auth` as the explicit Agent Identity
    path.
    - Document remote registration using `CODEX_API_KEY`.
    
    ## Big picture
    Callers can now provide an API key directly to `exec-server`
    registration without first establishing ChatGPT login state:
    
    ```sh
    CODEX_API_KEY="$OPENAI_API_KEY" \
    codex exec-server \
      --remote "https://<host>.openai.org/api" \
      --environment-id "$ENVIRONMENT_ID"
    ```
    
    ## Validation
    - `cargo fmt --all` (`just fmt` is not installed on this host)
    - `cargo test -p codex-cli -p codex-exec-server`
  • feat(tui): render cramped markdown tables as key-value records [2 of 2] (#24636)
    ## Stack
    
    - **Base: #24489 [1 of 2]** - render markdown tables in app style.
    - **Current: #24636 [2 of 2]** - render cramped markdown tables as
    key/value records.
    
    Review this PR against `fcoury/app-style-markdown-tables`; it contains
    only the fallback behavior for cramped tables.
    
    ## Why
    
    The row-separated markdown table rendering in #24489 remains readable
    while columns have usable room. Once long links or multiple prose-heavy
    columns are compressed into narrow allocations, however, the grid can
    turn words and paths into tall vertical strips that are difficult to
    scan. In those cases the content matters more than preserving the grid
    shape.
    
    ## What Changed
    
    <table>
    <tr><td>
    <p align="center"><b>
    Normal
    </b></p>
    <img width="1722" height="619" alt="CleanShot 2026-05-27 at 14 32 57"
    src="https://github.com/user-attachments/assets/d04f5fbd-6064-4acd-91bd-072d19b983df"
    />
    </td></tr>
    <tr><td>
    <p align="center"><b>
    Narrow
    </b></p>
    <img width="863" height="1013" alt="CleanShot 2026-05-27 at 14 33 12"
    src="https://github.com/user-attachments/assets/6a7d2968-0a68-48fd-ab5d-209b3dbaf03e"
    />
    </td></tr>
    <tr><td>
    <p align="center"><b>
    Very narrow
    </b></p>
    <img width="435" height="746" alt="CleanShot 2026-05-27 at 14 33 47"
    src="https://github.com/user-attachments/assets/f6a59e30-b1d2-4063-9c05-43933abc77d6"
    />
    </td></tr>
    </table>
    
    - Detect tables whose grid allocation causes systemic token
    fragmentation or starves multiple prose-heavy columns.
    - Render those tables as repeated key/value records instead of retaining
    an unreadable grid.
    - Use aligned label/value records when there is useful horizontal room,
    and switch to a stacked narrow-record layout where each label is
    followed by a full-width value when width is especially constrained.
    - Preserve the themed label color, rich inline formatting, links, and
    the existing grid presentation for tables that remain readable.
    - Add snapshot coverage for path-heavy narrow tables, prose-heavy issue
    tables, systemic compact fragmentation, and a control case that should
    continue to render as a grid.
    
    ## How to Test
    
    1. Start Codex from this branch and render a normal multi-column
    markdown table at a comfortable terminal width. Confirm it still appears
    as the styled row-separated grid from #24489.
    2. Render a table containing a long linked record identifier or
    file-like value, then narrow the terminal until the grid would split the
    value into vertical fragments. Confirm it switches to key/value records,
    with labels above values at very narrow widths.
    3. Render a table with multiple prose-heavy columns, such as an issue
    summary table with `Issue`, `Activity`, `Complexity`, and `Why start`.
    Confirm a cramped width switches to records rather than wrapping several
    columns into hard-to-read strips.
    4. Render a compact table where only one value wraps mildly. Confirm it
    stays in grid form rather than switching prematurely.
    
    ## Validation
    
    - Ran `just test -p codex-tui` while developing the fallback and
    reviewed/accepted the intended new markdown-render snapshots. The
    command still reports two unrelated existing guardian feature-flag test
    failures outside this diff.
    - Ran `just fix -p codex-tui` and `just fmt` after the Rust changes were
    complete.
    - `just argument-comment-lint` cannot reach source linting locally
    because Bazel fails while resolving LLVM sanitizer headers; touched
    positional literal callsites were inspected manually and annotated where
    needed.
  • feat(tui): add OSC 8 web links to rich content (#24472)
    ## Why
    
    Wrapped URLs in rich TUI output, especially URLs rendered inside
    Markdown tables, are split across terminal rows. In terminals that
    support OSC 8 hyperlinks, treating each visible fragment as part of the
    complete destination enables reliable open-link and copy-link actions
    even after table layout wraps the URL.
    
    This addresses the semantic-link portion of #12200 and the behavior
    described in
    https://github.com/openai/codex/issues/12200#issuecomment-4535452980. It
    does not change ordinary drag-selection across bordered table rows.
    
    ## What Changed
    
    - Added shared TUI OSC 8 support that validates `http://` and `https://`
    destinations, sanitizes terminal payloads, and applies metadata
    separately from visible line width/layout.
    - Added semantic web-link annotations to assistant and proposed-plan
    Markdown, including explicit web links and bare web URLs in prose and
    table cells while excluding code and non-web Markdown destinations.
    - Preserved complete URL targets through table wrapping, narrow pipe
    fallback, streaming, transcript overlay rendering, history insertion,
    and resize replay.
    - Routed intentional Codex-owned links in notices,
    status/setup/app-link, feedback, onboarding, MCP/plugin help, memories,
    and update surfaces through the shared hyperlink handling.
    
    ## How to Test
    
    1. Run Codex in a terminal with OSC 8 link support, such as Ghostty, and
    request an assistant response containing a Markdown table whose last
    column contains a long `https://` URL.
    2. Make the terminal narrow enough for the URL to wrap across multiple
    bordered table rows.
    3. Use the terminal's open-link or copy-link action on more than one
    wrapped URL fragment and confirm each fragment resolves to the complete
    original URL.
    4. Resize the terminal after the table is rendered and repeat the link
    action to confirm the destination survives scrollback replay.
    5. Open the transcript overlay while rich output is present and confirm
    web links remain interactive there.
    6. As a regression check, render inline/fenced code containing URL text
    and a Markdown link such as
    `[https://example.com](mailto:support@example.com)`; confirm these do
    not acquire a web OSC 8 destination.
    
    Targeted automated coverage exercised Markdown links and exclusions,
    wrapped and pipe-fallback tables, streaming/transcript overlay
    propagation, status-link truncation, and rendered word-wrapping cell
    alignment. `just test -p codex-tui` was also run; it passed the
    hyperlink coverage and reproduced two unrelated existing guardian
    feature-flag test failures.
  • fix(linux-sandbox): preserve shell cleanup on interruption (#22729)
    ## Why
    Interrupted `shell_command` calls can race with the outer tool-dispatch
    cancellation path. When that happens, the runtime future may be dropped
    before the spawned process gets a chance to run `SIGTERM` cleanup. For
    bwrapd-backed Linux sandbox commands, that can leave synthetic
    protected-path mount bookkeeping such as `.git/.codex` registrations
    under `/tmp` behind after a TUI interruption.
    
    The relevant cancellation points are the outer dispatch race in
    [`core/src/tools/parallel.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/parallel.rs#L91-L132)
    and the process shutdown logic in
    [`core/src/exec.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/exec.rs#L1367-L1393).
    
    ## What changed
    - Keep `shell_command` dispatch alive long enough for the runtime to
    finish cancellation cleanup instead of immediately returning the
    synthetic aborted response.
    - Fold shell-turn cancellation into the existing `ExecExpiration` path
    in
    [`core/src/tools/runtimes/shell.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/runtimes/shell.rs#L267-L274),
    so cancellation and timeout behavior stay centralized.
    - On cancellation, send `SIGTERM` first, wait briefly for cleanup to
    run, then hard-kill any remaining descendants in the original process
    group.
    - Treat `ESRCH` as an already-gone process-group cleanup case in
    `codex-utils-pty`, which keeps best-effort teardown from surfacing a
    stale-process race as an error.
    
    ## Verification
    - `cargo test -p codex-core cancellation`
    - Added regression coverage for:
      - `shell_tool_cancellation_waits_for_runtime_cleanup`
      - `process_exec_tool_call_cancellation_allows_sigterm_cleanup`
  • chore: enable namespace tools for Bedrock (#24713)
    Client-side namespace tools are now supported by bedrock. Enable
    `namespace_tools` for the Amazon Bedrock provider while continuing to
    disable unsupported hosted tools such as image generation and web
    search.
  • feat(tui): render markdown tables in app style [1 of 2] (#24489)
    ## Stack
    
    - **Current: #24489 [1 of 2]** - render markdown tables in app style.
    - **Stacked follow-up: #24636 [2 of 2]** - render cramped markdown
    tables as key/value records.
    
    ## Why
    
    Markdown tables currently render as boxed terminal grids, which gives
    ordinary assistant output a heavier visual treatment than surrounding
    rich text. This row-separated layout is the best match for how the App
    renders tables, while accented headers remain distinguishable even when
    a terminal font renders bold subtly.
    
    <table>
    <tr><td>
    <p align="center">Codex CLI - Before</p>
    <img width="1722" height="742" alt="CleanShot 2026-05-25 at 18 46 17"
    src="https://github.com/user-attachments/assets/f673d92a-ebd8-46e2-b414-3d985e41b6a4"
    />
    </td></tr>
    <tr><td>
    <p align="center">Codex CLI - After</p>
    <img width="1720" height="957" alt="image"
    src="https://github.com/user-attachments/assets/36a3d331-bea1-439b-b5be-e97b0731bd6f"
    />
    </td></tr>
    <tr><td>
    <p align="center">Codex App</p>
    <img width="979" height="1293" alt="CleanShot 2026-05-25 at 18 45 04"
    src="https://github.com/user-attachments/assets/7d97cae0-9256-4f6e-a4b3-8b8f22b0d901"
    />
    </td></tr>
    </table>
    
    ## What Changed
    
    - Render markdown tables as padded, aligned rows without an enclosing
    box.
    - Style table headers with the active syntax-theme accent plus bold
    text, while keeping separators low contrast and theme-aware.
    - Use a segmented heavy header rule and thin body-row rules, preserving
    wrapping, narrow-width fallback, streaming parity, and rich-history
    rendering.
    - Update focused assertions and snapshots for the final table layout.
    
    ## How to Test
    
    1. Render a markdown table in the TUI with several rows and columns.
    2. Confirm the header uses the active theme accent, rows use
    one-character interior padding, and the table has no enclosing box.
    3. Confirm the header is followed by segmented `━` rules and multiple
    body rows are separated by muted segmented `─` rules.
    4. Render the same table while streaming and in history/raw-mode
    toggles; the final rich layout should remain stable.
    5. Render a narrow table with long content and verify wrapping or pipe
    fallback does not overflow.
    
    ## Validation
    
    - `just test -p codex-tui table`
    - `just test -p codex-tui streaming::controller::tests`
    - `just argument-comment-lint-from-source -p codex-tui -- --all-targets`
    - `just fix -p codex-tui`
    - `just fmt`
    
    `just test -p codex-tui` was also run after accepting the snapshots; it
    fails only in the unrelated existing guardian app tests
    `update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    and
    `update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`.
  • feat(tui): make turn interruption keybind configurable (#24766)
    ## Why
    
    Interrupting an active turn is currently fixed to `Esc`, which is easy
    to hit accidentally and cannot be customized through `/keymap`. This
    gives users a less accidental binding while preserving the existing
    default.
    
    ## What Changed
    
    - Adds `tui.keymap.chat.interrupt_turn` to `/keymap`, defaulting to
    `esc` and supporting remapping or unbinding.
    - Uses the configured interrupt binding for running-turn status, queued
    steer interruption, and `request_user_input`, including the visible
    hints.
    - Preserves local `Esc` behavior for popups, Vim insert mode, and
    `/agent` editing while validating conflicts with fixed/backtrack and
    request-input navigation bindings.
    - Adds behavior and snapshot coverage for remapped interruption paths.
    
    ## How to Test
    
    1. Run Codex and open `/keymap`, then set **Interrupt Turn** to `f12`.
    2. Start a turn and confirm `Esc` no longer interrupts it while `f12`
    does; the running hint should display `f12 to interrupt`.
    3. Queue a steer while a turn is running and confirm the preview
    displays `f12`; pressing it should interrupt and submit the steer
    immediately.
    4. Trigger a `request_user_input` prompt and confirm its footer uses
    `f12`; with notes open, `Esc` should still clear notes while `f12`
    interrupts the turn.
    5. Clear the Interrupt Turn binding and confirm the key-specific
    interrupt hint is removed while `Ctrl+C` remains available.
    
    Targeted validation:
    
    - `just write-config-schema`
    - `just fix -p codex-config`
    - `just fix -p codex-tui`
    - `just fmt`
    - `just argument-comment-lint-from-source -p codex-config -p codex-tui`
    - `just test -p codex-config`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    - `just test -p codex-tui keymap_setup::tests`
    - `just test -p codex-tui` (fails in two pre-existing guardian
    feature-flag tests unrelated to this diff; the intentional picker
    snapshot updates were reviewed and accepted)
  • feat(tui): add vim text object bindings (#24382)
    ## Why
    
    Vim mode currently supports some normal-mode operators and motions, but
    common text-object combinations like `ciw`, `daw`, `di(`, and
    quote/bracket variants are still missing. That makes the composer feel
    incomplete for users who expect operator + text object editing to work
    inside prompts.
    
    Closes #21383.
    
    ## What Changed
    
    - Add Vim pending-state support for operator/text-object sequences.
    - Add `c` as a normal-mode operator for text objects, so combinations
    like `ciw` delete the object and enter insert mode.
    - Support word, WORD, delimiter, and quote text objects:
      - `iw`, `aw`, `iW`, `aW`
      - `i(`, `a(`, `i)`, `a)`, `ib`, `ab`
      - `i[`, `a[`, `i]`, `a]`
      - `i{`, `a{`, `i}`, `a}`, `iB`, `aB`
      - `i"`, `a"`, `i'`, `a'`, `i\``, `a\``
    - Add configurable keymap entries and keymap picker coverage for the new
    Vim text-object context.
    - Regenerate the config schema and update keymap picker snapshots.
    
    ## How to Test
    
    Manual smoke test:
    
    1. Start Codex with Vim composer mode enabled.
    2. Type a draft such as:
       ```text
       alpha beta gamma
       call(foo[bar], {"x": "hello world"})
       say "one \"two\" three" now
       ```
    3. Put the cursor on `beta`, press `ciw`, and confirm `beta` is removed
    and the composer enters insert mode.
    4. Escape back to normal mode, put the cursor on `gamma`, press `daw`,
    and confirm `gamma` plus surrounding whitespace is removed.
    5. Put the cursor inside `foo[bar]`, press `di[`, and confirm only `bar`
    is removed.
    6. Put the cursor inside `call(...)`, press `da(`, and confirm the whole
    parenthesized section is removed.
    7. Put the cursor inside the quoted text, press `ci"`, and confirm the
    quote contents are removed and insert mode starts.
    8. Verify cancellation does not edit text: press `d` then `Esc`, and
    press `d` then `i` then `Esc`.
    
    Targeted tests:
    
    - `cargo test -p codex-tui --lib vim_`
    - `cargo nextest run -p codex-tui keymap_setup::tests`
    
    Additional local checks:
    
    - `just write-config-schema`
    - `just fmt`
    - `just fix -p codex-tui`
    - `git diff --check`
    - `cargo insta pending-snapshots --manifest-path tui/Cargo.toml`
    
    Local full-suite note: `just test -p codex-tui` ran to completion. The
    keymap snapshot failures were expected and accepted. Two unrelated
    guardian feature-flag tests still fail locally:
    -
    `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`
    
    `just argument-comment-lint` is currently blocked locally by Bazel
    analysis before the lint runs because `compiler-rt` has an empty
    `include/sanitizer/*.h` glob in the local Bazel cache. The touched Rust
    diff was manually inspected for opaque positional literals.
  • [codex] add compaction metadata to turn headers (#24368)
    ## Summary
    - Add `request_kind` values for foreground turn, startup prewarm,
    compaction, and detached memory model requests.
    - Attach compaction dispatch metadata to local Responses, legacy
    `/v1/responses/compact`, and remote v2 compact requests.
    - Add the existing logical context-window identifier as `window_id` on
    turn-owned model request metadata.
    - Keep identity fields optional for detached memory requests, while
    still emitting `request_kind="memory"` in non-git/no-sandbox workspaces.
    
    ## Root Cause
    `x-codex-turn-metadata` has more than one producer. Foreground turns and
    compaction requests own a real turn and should carry that turn identity.
    Detached memory stage-one requests do not own a foreground turn, so
    absent identity fields are valid rather than missing data. Startup
    websocket prewarm is also a model request, but it has `generate=false`
    and must not be counted as a foreground turn.
    
    `thread_source` or session source identifies where a thread came from
    (for example review, guardian, or another subagent). `request_kind`
    identifies what the current outbound model request is doing (`turn`,
    `prewarm`, `compaction`, or `memory`). A review or guardian thread can
    issue either a normal turn request or a compaction request, so source
    cannot replace request kind.
    
    ## Behavior / Impact
    - Ordinary foreground requests send `request_kind="turn"`, their real
    identity fields, and `window_id="<thread_id>:<window_generation>"`.
    - Startup websocket warmup requests send `request_kind="prewarm"` so
    they are not counted as foreground turns.
    - Compaction requests send `request_kind="compaction"`, their real
    owning turn identity, the existing `window_id`, and
    `compaction.{trigger,reason,implementation,phase,strategy}`.
    - Detached memory stage-one requests send `request_kind="memory"`
    without `session_id`, `thread_id`, `turn_id`, or `window_id`; when no
    workspace metadata exists, the kind-only header is still emitted.
    - `session_id`, `thread_id`, `turn_id`, and `window_id` remain optional
    in the header schema because detached memory requests do not own a
    foreground turn or context window.
    - `window_id` is not a new ID system: it is copied from the already-sent
    `x-codex-window-id` / WS client metadata value at model-request dispatch
    time.
    - Existing `x-codex-window-id` HTTP/WS emission, value format,
    generation advancement, resume behavior, and fork reset behavior are
    unchanged.
    - `request_kind`, `window_id`, and upstream turn-owned identity fields
    remain schema-owned; input `responsesapi_client_metadata` cannot replace
    their canonical values.
    - No table, DAG, export, app-server API, or MCP `_meta` schema changes
    are included.
    
    A compaction attempt stopped by a pre-compact hook issues no model
    request and therefore has no request header; its outcome remains in
    analytics events. Status, error, duration, and token deltas also remain
    analytics fields rather than request-header fields.
    
    Future detached-memory attribution using a real initiating turn ID as
    `trigger_turn_id` is intentionally not part of this PR.
    
    ## Sync With Main
    - Final pushed head `716342e79` is rebased onto `origin/main@0d37db4b2`.
    - The metadata conflict came from upstream `#24160`, which added
    `forked_from_thread_id` on the same `turn_metadata` surface. Resolution
    preserves that field and its protection from client metadata override
    alongside this PR's request-kind, compaction, and window-id fields.
    - While resolving the overlapping commits, I removed an accidental
    recursive model-request overlay and a duplicate detached-memory header
    builder before completing the rebase.
    
    ## Latency / User Experience Boundary
    - Foreground turns perform no new filesystem, git, or network work. New
    fields are inserted into metadata already serialized for outgoing
    requests.
    - Compaction issues the same model/HTTP requests with the same prompt,
    model, service tier, and sampling settings; only metadata bytes change.
    - Startup prewarm already sent metadata; it is now correctly classified
    as `prewarm`.
    - Non-git detached memory now sends a small kind-only metadata header
    rather than no header.
    - This client diff adds no user-visible latency mechanism beyond
    negligible serialization and header bytes on already-existing requests.
    
    ## Validation
    On conflict-resolved head `1d35c2cfb` based on `origin/main@487521733`:
    - `just fmt` (passed)
    - `just fix -p codex-core` (passed)
    - `git diff --check origin/main...HEAD` (passed)
    - `just test -p codex-core -E 'test(turn_metadata) |
    test(websocket_first_turn_uses_startup_prewarm_and_create) |
    test(responses_stream_includes_turn_metadata_header_for_git_workspace_e2e)
    |
    test(responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create)
    | test(remote_compact_v2_retries_failures_with_stream_retry_budget) |
    test(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork)'`
    (`23 passed`; `bench-smoke` passed)
    - `just test -p codex-app-server -E
    'test(turn_start_forwards_client_metadata_to_responses_request_v2) |
    test(turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2)
    | test(auto_compaction_remote_emits_started_and_completed_items)'` (`3
    passed`; `bench-smoke` passed)
    - `just test -p codex-memories-write` (`29 passed`; `bench-smoke`
    passed)
  • [codex] Remove stale composer narrative doc references (#24641)
    ## Context
    
    `docs/tui-chat-composer.md` was removed by #20896 as part of removing
    local-only docs/specs from the repository. I checked the #20896 file
    list and the merge commit: the composer doc was deleted, not moved or
    copied, and current `main` does not contain a replacement composer
    narrative doc.
    
    Current guidance should keep contributors and agents focused on the docs
    that still exist: the module docs in `chat_composer.rs` and
    `paste_burst.rs`.
    
    ## Summary
    
    - Removes the scoped TUI bottom-pane AGENTS.md requirement to update
    `docs/tui-chat-composer.md`.
    - Removes stale module-doc references to that deleted narrative doc from
    `chat_composer.rs` and `paste_burst.rs`.
    
    ## Validation
    
    - Checked #20896 and the merge commit with rename/copy detection to
    confirm `docs/tui-chat-composer.md` was deleted rather than moved.
    - Searched current `main` for a replacement composer narrative doc.
    - Not run; documentation-only change.
  • fix: Preserve draft text when completing argument-taking slash commands (#23950)
    This adds slash command completion behavior for argument-taking
    commands, where text after the partially typed command becomes inline
    arguments instead of being discarded. This addresses the workflow of
    drafting text first, moving to the start, and completing a slash command
    around that existing draft. Before this change, this workflow would
    remove all user-input text aside from the slash command, which can be
    frustrating if the user had just typed out a long and well thought out
    goal.
    
    - Preserves the draft tail for inline-argument slash commands like
    `/goal` and `/review` when completing with `Tab` or `Enter`.
    - Keeps popup filtering focused on the command fragment under the cursor
    rather than the full draft text.
    - Leaves slash commands that do not support inline arguments unchanged,
    so completion still replaces the existing draft tail for those commands.
    - Adds focused TUI tests under slash input covering preserved arguments,
    cursor edge cases, and the negative case for a command without inline
    args.
      
    Follow-up simplification and test relocation from #24683 folded into
    this PR.
    
    ---------
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • fix: run standalone updates noninteractively (#24637)
    # Summary
    
    The standalone update action currently downloads and runs the Codex
    installer as an interactive command. When an existing managed Codex
    install is present, accepting an update can therefore enter an installer
    prompt instead of completing the update.
    
    This change runs the standalone installer with `CODEX_NON_INTERACTIVE=1`
    on macOS/Linux and Windows. The installer environment-variable support
    is introduced by the parent PR; this PR wires that behavior into the
    Codex CLI update action. The rendered Windows command remains
    shell-safe, and long update commands wrap within the update-notice card.
    The standard test target snapshots the standalone notice for both
    platforms.
    
    # Stack
    
    1. [#21567](https://github.com/openai/codex/pull/21567) - Adds
    environment-controlled release selection and noninteractive installer
    behavior.
    2. [#24637](https://github.com/openai/codex/pull/24637) - Runs
    standalone updates with `CODEX_NON_INTERACTIVE=1`. (current)
    3. [#24639](https://github.com/openai/codex/pull/24639) - Removes
    explicit release argument inputs in favor of `CODEX_RELEASE`.
    
    # Evidence
    
    Standalone updater-shaped macOS install with an existing npm-managed
    Codex on `PATH`:
    
    
    https://github.com/user-attachments/assets/a27fe9e9-db3a-4c39-a514-24bd3d1f01e8
    
    # Testing
    
    Tests: targeted `codex-tui` update-action and update-notice snapshot
    tests, Rust formatting, benchmark smoke validation, macOS live-terminal
    standalone-update smoke testing, Windows ARM64 PowerShell
    standalone-update smoke testing through Parallels, and CI.
  • Bump SQLx to pick up newer bundled SQLite (#24728)
    ## Why
    
    Codex stores thread, log, goal, and memory state in bundled SQLite
    databases through SQLx. We have a suspected SQLite WAL-reset corruption
    issue under heavy concurrent writer load, especially when multiple
    subagents are active. The existing `sqlx 0.8.6` dependency kept us on an
    older `libsqlite3-sys` / bundled SQLite, so this PR moves the SQLx stack
    far enough forward to pick up the newer bundled SQLite library.
    
    ## What changed
    
    - Bump the workspace `sqlx` dependency to `0.9.0`.
    - Use the SQLx 0.9 feature names explicitly: `runtime-tokio`,
    `tls-rustls`, and `sqlite-bundled`.
    - Update `Cargo.lock` so `sqlx-sqlite` resolves through `libsqlite3-sys
    0.37.0`.
    - Refresh `MODULE.bazel.lock` for the dependency changes.
    - Adapt `codex-state` to SQLx 0.9:
    - build dynamic state queries with `QueryBuilder<Sqlite>` instead of
    passing dynamic `String`s to `sqlx::query`;
    - remove the old `QueryBuilder` lifetime parameter from helper
    signatures;
    - preserve SQLx's new `Migrator` fields when constructing runtime
    migrators.
    
    ## Verification
    
    - `just test -p codex-state`
    - `just bazel-lock-check`
    - `cargo check -p codex-state --tests`
  • fix(tui): complete vim word-end and line-end behavior (#24380)
    ## Why
    
    The TUI Vim composer currently diverges from normal Vim editing in two
    common workflows: pressing `e` repeatedly can remain stuck at an
    existing word end, and normal mode does not support `C` for changing
    through the end of the line. The existing `D` behavior also removes the
    newline when the cursor is already at the line boundary, which makes the
    new `C` action and existing deletion action surprising in multiline
    prompts.
    
    Closes #23926.
    Closes #24238.
    
    ## What Changed
    
    - Make normal-mode `e` advance from the current word end to the next
    word end, including for operator motions such as `de`.
    - Add configurable Vim normal-mode `change_to_line_end` behavior, bound
    to `C` by default, which deletes to the end of the current line and
    enters Insert mode.
    - Keep the newline intact when `D` or `C` is pressed at the end-of-line
    boundary.
    - Add regression coverage for repeated `e`, `de`, `C`, and the multiline
    `C`/`D` boundary behavior.
    - Regenerate the config schema and update the keymap picker snapshots
    for the new Vim action.
    
    ## How to Test
    
    1. Run Codex with Vim composer mode enabled:
       ```bash
       cd codex-rs
       cargo run --bin codex -- -c tui.vim_mode_default=true
       ```
    2. Enter `alpha beta gamma`, press `Esc`, `0`, then press `e`
    repeatedly.
    Confirm the cursor advances through the ends of `alpha`, `beta`, and
    `gamma`.
    3. Enter `hello world`, press `Esc`, `0`, `w`, then `C`.
       Confirm `world` is deleted and the composer enters Insert mode.
    4. Enter a multiline prompt with `hello` above `world`, press `Esc`,
    `k`, `$`, and then `D`.
       Confirm the newline is preserved and the two lines do not join.
    5. At the same boundary, press `C` and type `!`.
    Confirm the composer enters Insert mode and yields `hello!` above
    `world`, preserving the newline.
    
    Targeted automated verification:
    - `just fix -p codex-tui`
    - `just argument-comment-lint-from-source -p codex-tui -p codex-config`
    - `cargo insta pending-snapshots` reports no pending snapshots.
    - `just test -p codex-tui` validates the new Vim and keymap snapshot
    coverage, but the command remains red due to two reproducible unrelated
    failures in `app::tests::update_feature_flags_disabling_guardian_*`.
    
    ## Validation Note
    
    The workspace-wide `just argument-comment-lint` form is currently
    blocked during Bazel analysis by the existing LLVM `compiler-rt` missing
    `include/sanitizer/*.h` failure; package-scoped source linting for the
    changed Rust crates passed.
  • TUI config cleanup: plugin marketplace (#24257)
    ## Why
    
    Plugin and marketplace mutations are applied by the app server, but
    several TUI follow-up paths still refreshed state from the TUI host
    config. In remote workspace mode, that can leave plugin UI state tied to
    stale client-local `config.toml` after the server has already applied
    the mutation.
    
    ## What
    
    - Stop reloading the TUI host config after app-server-owned plugin,
    marketplace, skill, and app mutations.
    - Use the same app-server-owned refresh path for local and remote
    sessions: ask the app server to reload user config where the running
    session needs it, then refetch plugin list/detail state from the app
    server.
    - Build plugin mention candidates from existing app-server `plugin/list`
    and `plugin/read` data in both local and remote sessions instead of
    TUI-host plugin config.
    - Avoid the duplicate local config reload after `ReloadUserConfig` asks
    the app server to reload config.
    
    ## Verification
    
    Manually launched a local WebSocket app-server with a temp server
    `CODEX_HOME`, launched the TUI with a separate temp host `CODEX_HOME`
    and `--remote`, installed a sample plugin from a temp local marketplace
    through `/plugins`, and confirmed the TUI refreshed to installed state
    while only the server config gained `[plugins."sample@debug"]`. Trace
    logs showed the TUI using app-server `plugin/list` and `plugin/read` for
    the refresh path.
  • Drop startup context when truncating forked rollouts (#24751)
    ## Summary
    - Change last-`n` fork truncation to start at the first fork-turn
    boundary instead of returning the full rollout when the fork history is
    shorter than the requested window.
    - Add coverage for the startup-prefix case in both rollout truncation
    tests and agent control spawn behavior.
    - Ensure bounded forked children still rebuild context after the cached
    prefix is truncated.
    
    ## Testing
    - Added unit coverage for truncation behavior when the parent history is
    under the requested fork-turn limit.
    - Added an agent control test covering bounded fork spawn behavior with
    startup context present.
    - Not run (not requested).
  • feat: add thread idle lifecycle hook (#24744)
    ## Why
    
    Extensions can currently observe thread start, resume, and stop, but
    they do not have a lifecycle point for the host to say that immediately
    pending thread work has drained. That makes idle follow-up behavior
    harder to express as extension-owned logic instead of host-specific
    plumbing.
    
    This adds an explicit idle lifecycle hook so an extension can react when
    a thread becomes idle while the host keeps ownership of whether any
    submitted follow-up input starts a turn, is queued, or is ignored.
    
    ## What changed
    
    - Added `ThreadIdleInput` with access to the session-scoped and
    thread-scoped extension stores.
    - Added a default `on_thread_idle` method to
    `ThreadLifecycleContributor`.
    - Re-exported `ThreadIdleInput` from the extension API surface.
    
    ## Testing
    
    Not run; this only extends the extension API trait surface with a
    default hook and exported input type.
  • Fix guardian review test user input (#24746)
    ## Summary
    - Add the missing additional_context field to the guardian review
    Op::UserInput test initializer.
    
    ## Test plan
    - just fmt
    - just test -p codex-core guardian_review
    - just test -p codex-core (compiles, then fails on local environment
    issues: sandbox-exec Operation not permitted, missing test_stdio_server
    helper binary, and unrelated timeouts)
  • feat: handle goal usage limits in goal extension (#24628)
    ## Why
    
    The extracted goal runtime needs a host-callable path for turns that
    stop because the workspace usage limit is reached. In that case, any
    in-turn goal progress should be accounted before the goal becomes
    terminal, and active goal accounting must be cleared so later
    tool-finish or turn-stop handling does not keep charging usage to a
    stopped goal.
    
    ## What changed
    
    - Adds `GoalRuntimeHandle::usage_limit_active_goal_for_turn`, which
    accounts current active-goal progress, marks the active or
    budget-limited thread goal as `UsageLimited`, records terminal metrics
    when the status changes, clears active goal accounting, and emits the
    updated goal event.
    - Covers both active and budget-limited goals in
    `ext/goal/tests/goal_extension_backend.rs`, including the invariant that
    later token/tool events do not add usage after the goal has been
    usage-limited.
    
    ## Testing
    
    - Added
    `usage_limit_active_goal_accounts_progress_and_clears_accounting`.
    - Added `usage_limit_budget_limited_goal_accounts_remaining_progress`.
  • Revert "Add Bedrock Mantle GovCloud region (#23860)" (#24690)
    This reverts commit 5381240f57. Gov cloud
    should not be supported
    
    # External (non-OpenAI) Pull Request Requirements
    
    External code contributions are by invitation only. Please read the
    dedicated "Contributing" markdown file for details:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • fix(auto-review) skip legacy notify for auto review threads (#24714)
    ## Summary
    Clear inherited legacy `notify` from Guardian review session config,
    since we should not be passing auto review threads into `notify`
    targets. Keeps legacy notify payload and hook runtime behavior unchanged
    for normal user turns.
    
    ## Testing
    - [x] add a Guardian config regression and dedicated Guardian
    integration test so review sessions cannot inherit parent notify hooks
  • Allow runtime enablement for remote plugins (#24707)
    experimentalFeature/enablement/set now accepts remote_plugin as a
    supported runtime feature key
  • Uprev Rust toolchain pins to 1.95.0 (#24684)
    ## Summary
    - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
    Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
    environment config.
    - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
    match the new version.
    - Leave purpose-specific toolchains unchanged, including the
    `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
    build pin.
    - Includes fixes for new lints from `just fix` and a few codex-authored
    fixes for lints without a suggestion.
  • fix(core): instrument stalled tool-listing handoff (#24667)
    ## Why
    
    When a turn needs a follow-up request after tool output is recorded,
    Codex can still appear stuck in `Thinking` before the next `/responses`
    request is opened. The existing local trace showed the last completed
    response and the absence of a new backend request, but it did not show
    whether the stall was in tool-router preparation or later request setup.
    
    Issue: N/A (internal incident investigation)
    
    ## What Changed
    
    Added trace spans around the pre-stream tool-router handoff in
    `core/src/session/turn.rs`, including the `built_tools` phase and the
    MCP manager read lock.
    
    Added per-server MCP tool-listing spans and trace breadcrumbs in
    `codex-mcp/src/connection_manager.rs` with startup snapshot /
    startup-complete state so a pending MCP client is visible in feedback
    logs instead of looking like a silent hang.
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-mcp`
    - `just test -p codex-core` (prior full rerun fails in this workspace on
    unrelated integration tests: code-mode output length expectations, one
    shell timeout formatting assertion, and shell snapshot timeouts; latest
    review-fix rerun compiled and passed 1160 tests before I stopped the
    abnormally slow unrelated suite)
  • fix: dont compact standalone websearch schema (#24660)
    add new `parse_tool_input_schema_without_compaction` to bypass the
    existing compaction/trimming of client-provided tool schemas that are
    over 4k bytes.
    
    we want this for standalone web search to keep field guidance/metadata
    on certain fields; this keeps us closer to parity with existing hosted
    tool schema (which didnt go through this 4k byte filter).
  • [codex] Remove obsolete goal continuation turn marker (#24658)
    ## Why
    
    `continuation_turn_id` was introduced to distinguish synthetic goal
    continuation turns for the no-tool continuation suppression heuristic.
    #20523 removed that heuristic, but left the marker behind. It is still
    written and cleared without affecting any runtime decision.
    
    ## What Changed
    
    - Remove `GoalRuntimeState::continuation_turn_id`.
    - Remove the marker setter/clearer and their now-no-op start, finish,
    and abort call sites.
    
    ## Testing
    
    - Not run yet (deferred at request).
  • [codex-analytics] add grouped session id to runtime events (#24655)
    ## Why
    - Runtime analytics events report `thread_id`, which identifies the
    individual thread emitting an event
    - They don't report `session_id`, which identifies the shared session
    for a root thread and its subagent threads
    - Emitting both identifiers allows analytics to group related activity
    
    ## What Changed
    - Adds `session_id` to relevant analytics events (thread_initalized,
    turn, turn_steer, compaction, guardian_review)
    - Tracks each thread's session ID in the analytics reducer so subsequent
    thread scoped events emit the same value
    - Carries the shared session ID through subagent initialization
    
    ## Verification
    - `just test -p codex-analytics` validates event payloads and subagent
    session grouping.
    - Focused `codex-app-server` tests validate session IDs for thread,
    turn, and steer events.
    - Focused `codex-core` tests validate root and subagent session ID
    propagation.
  • Restore legacy image detail values (#24644)
    ## Why
    
    Older persisted rollouts can contain `input_image.detail` values of
    `auto` or `low` from before `ImageDetail` was narrowed to
    `high`/`original`. Current deserialization rejects those values, which
    can make resume skip later compacted checkpoints and reconstruct an
    oversized raw suffix before the next compaction attempt.
    
    Confirmed Sentry reports fixed by this compatibility path:
    
    - [CODEX-1H3F](https://openai.sentry.io/issues/7500642496/)
    - [CODEX-1H6N](https://openai.sentry.io/issues/7501025347/)
    - [CODEX-1JDP](https://openai.sentry.io/issues/7504549065/)
    - [CODEX-1HW6](https://openai.sentry.io/issues/7503407986/)
    
    ## Background
    
    [openai/codex#20693](https://github.com/openai/codex/pull/20693) added
    image-detail plumbing for app-server `UserInput` so input images could
    explicitly request `detail: original`. The Slack discussion behind that
    PR was about ScreenSpot / bridge evals where user input images were
    resized, while tool output images already had MCP/code-mode ways to
    request image detail.
    
    In review, the intended new API surface was narrowed to `high` and
    `original`: default to `high`, allow `original` when callers need
    unchanged image handling, and avoid encouraging new `auto` or `low`
    usage. That policy still makes sense for newly emitted values.
    
    The missing compatibility piece is persisted history. Older rollouts can
    already contain `auto` and `low`, and resume reconstructs typed history
    by deserializing those rollout records. Rejecting old values at that
    boundary causes valid compacted checkpoints to be skipped. This PR
    restores `auto` and `low` as real variants so old records deserialize
    and round-trip without being rewritten as `high`, while product paths
    can continue to default to `high` and avoid emitting `auto` for new
    behavior.
    
    ## What changed
    
    - Restored `ImageDetail::Auto` and `ImageDetail::Low` as first-class
    protocol values.
    - Preserved `auto`/`low` through rollout deserialization, MCP image
    metadata, code-mode image output, and schema/type generation.
    - Kept local image byte handling conservative: only `original` switches
    to original-resolution loading; `auto`/`low`/`high` continue through the
    resize-to-fit path while retaining their detail value.
    - Added regression coverage for enum round-tripping and code-mode `low`
    detail handling.
    
    ## Testing
    
    - `just write-app-server-schema`
    - `just test -p codex-protocol`
    - `just test -p codex-tools`
    - `just test -p codex-code-mode`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core
    suite::rmcp_client::stdio_image_responses_preserve_original_detail_metadata`
    - `just test -p codex-core
    suite::code_mode::code_mode_can_use_mcp_image_result_with_image_helper`
    - Loaded broken rollouts on local fixed builds, and started/completed
    new turns.
    
    I also attempted `just test -p codex-core`; the local broad run did not
    finish green: 2559 tests run, 2467 passed, 55 flaky, 91 failed, 1 timed
    out. The failures were broad timeout/deadline failures across unrelated
    areas; targeted changed-path core tests above passed.
  • Attach Windows sandbox log to feedback reports (#24623)
    ## Why
    
    Windows sandbox diagnostics are currently hard to recover from
    `/feedback` even though they are often the most useful artifact when
    debugging sandbox behavior. Now that sandbox logging uses daily rolling
    files, feedback can safely include the current day's sandbox log without
    uploading the old ever-growing legacy `sandbox.log`.
    
    ## What changed
    
    - Add a `codex-windows-sandbox` helper that resolves the current daily
    sandbox log from `codex_home`.
    - When feedback is submitted with logs enabled on Windows, app-server
    attaches today's sandbox log if it exists.
    - Upload the attachment under the stable filename `windows-sandbox.log`,
    independent of the dated on-disk filename.
    - Keep existing raw `extra_log_files` behavior unchanged for rollout and
    desktop log attachments.
    
    ## Verification
    
    - `cargo fmt -p codex-app-server -p codex-windows-sandbox`
    - `cargo test -p codex-windows-sandbox
    current_log_file_path_for_codex_home_uses_sandbox_dir`
    - `cargo test -p codex-app-server
    windows_sandbox_log_attachment_uses_current_log`
    - Manual CLI/TUI `/feedback` test confirmed Sentry received
    `windows-sandbox.log`.
  • [codex] remove plain image wrapper spans (#24652)
    ## Why
    
    Remote image submissions currently wrap native `input_image` spans in
    literal `<image>` and `</image>` text spans. Those extra prompt tokens
    add structure without providing label or routing information.
    
    ## What Changed
    
    - Serialize `UserInput::Image` directly as an `input_image` content
    span.
    - Preserve named local-image framing and legacy wrapper parsing for
    labeled attachments and existing histories.
    - Update existing request-shape expectations for drag-and-drop images,
    model switching, and compaction.
    
    ## Validation
    
    - `just test -p codex-protocol`
    - Focused `codex-core` run covering
    `drag_drop_image_persists_rollout_request_shape`,
    `model_change_from_image_to_text_strips_prior_image_content`, and
    `snapshot_request_shape_pre_turn_compaction_including_incoming_user_message`
    
    ## Notes
    
    - A broader `just test -p codex-core` run was attempted; the affected
    tests passed, while the overall run failed in unrelated CLI, MCP, and
    tooling tests plus a `thread_manager` timeout.
  • windows-sandbox: remove SandboxPolicy runner plumbing (#23813)
    ## Why
    
    The Windows sandbox runner still carried the old `SandboxPolicy`
    compatibility path even though core now computes `PermissionProfile`.
    That meant Windows command-runner execution could only see the legacy
    projection, so profile-only filesystem rules such as deny globs were not
    part of the runner input.
    
    ## What Changed
    
    - Removed the Windows-local `SandboxPolicy` parser/export and deleted
    `windows-sandbox-rs/src/policy.rs`.
    - Changed restricted-token capture/session setup, elevated setup,
    world-writable audit, read-root grant, and command-runner session APIs
    to accept `PermissionProfile` plus the profile cwd.
    - Bumped the elevated command-runner IPC protocol to version 2 because
    `SpawnRequest` now carries `permission_profile` /
    `permission_profile_cwd` instead of the legacy `policy_json_or_preset` /
    `sandbox_policy_cwd` fields.
    - Updated core exec, unified exec, debug-sandbox, TUI setup/grant flows,
    and app-server setup to pass the actual effective `PermissionProfile`.
    - Left regression coverage asserting the old IPC policy fields are
    absent and the runner serializes tagged `PermissionProfile` JSON.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-app-server
    request_processors::windows_sandbox_processor`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-app-server
    -p codex-cli -p codex-tui`
    - `just fix -p codex-cli -p codex-tui`
    - `just fix -p codex-windows-sandbox -p codex-tui`
    - `rg "\\bSandboxPolicy\\b" codex-rs/windows-sandbox-rs` returned no
    matches.
    
    Note: `cargo test -p codex-cli` was attempted but did not reach crate
    tests because local disk filled while compiling dependencies (`No space
    left on device`). The targeted clippy pass compiled the affected CLI/TUI
    surfaces afterward.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23813).
    * #24108
    * __->__ #23813
  • Avoid repeated marketplace upgrades for alternate layouts (#24320)
    Fixes #24249.
    
    ## Why
    
    Codex already supports discovering marketplaces under both
    `.agents/plugins/marketplace.json` and
    `.claude-plugin/marketplace.json`. The Git marketplace auto-upgrade
    no-op check only looked for the `.agents` layout. That meant an
    installed `.claude-plugin` marketplace with matching revision metadata
    still looked absent, so plugin list/startup upgrade work could stage and
    re-activate the same marketplace again.
    
    That matches the failure shape in #24249: the report called out repeated
    marketplace sync/cache refresh logs and a large recently-touched
    `.tmp/marketplaces/.staging` directory. This change makes the
    auto-upgrade path recognize the installed `.claude-plugin` marketplace
    as already current, which should remove that staging/activation feedback
    loop.
    
    ## What changed
    
    `codex-rs/core-plugins/src/marketplace_upgrade.rs` now uses the existing
    supported marketplace manifest discovery helper when deciding whether an
    installed Git marketplace is already current. Existing local plugin
    source validation is unchanged; `source: "./"` still remains invalid.
    
    ## Confidence
    
    Confidence is high that this fixes the repeated marketplace upgrade
    path: the old hardcoded layout check was definitely wrong for installed
    `.claude-plugin` marketplaces, and the reported staging churn points
    directly at that path.
    
    Confidence is not 100% because we do not have a CPU profile or a fully
    re-run reporter repro. A malformed marketplace entry can still be logged
    as invalid if another caller repeatedly lists plugins; this PR fixes the
    staging/upgrade feedback loop that likely made the failure pathological,
    not every possible source of repeated marketplace resolution.
  • TUI config cleanup: plugin mentions (#24266)
    ## Summary
    
    TUI plugin mention refresh still joined app-server plugin inventory with
    client-local plugin config, which can diverge once plugin state is owned
    by the app server.
    
    This changes the TUI to mirror the GUI client: `plugin/list` is the
    autocomplete source, and mention candidates are plugin-level entries
    filtered to installed, enabled, and not disabled by admin. The TUI no
    longer reads local plugin config or calls `plugin/read` while refreshing
    plugin mention candidates.
    
    ## API shape and limitations
    
    The current app-server API does not expose effective per-session plugin
    capability summaries for mention autocomplete. As in the GUI,
    autocomplete now trusts `plugin/list` metadata rather than proving which
    plugin capabilities are loaded in the active session.
    
    That avoids stale client-local reads and the cwd/remote detail gaps in
    `plugin/read`, but intentionally accepts the same list-level tradeoff as
    the app: if `plugin/list` reports a remote plugin before its local
    bundle is materialized, the plugin can still appear as a mention
    candidate.
  • make direct only allowed caller for standalone websearch (#24646)
    only allow `Direct` callers of the standalone websearch tool because its
    not supported in codemode
  • Add forked_from_thread_id turn metadata (#24160)
    ## Why
    
    When Codex calls responsesapi, we currently send `session_id`,
    `thread_id`, and `turn_id` among other things as
    `client_metadata["x-codex-turn-metadata"]`. This PR adds
    `forked_from_thread_id` which helps explain the "lineage" of a forked
    thread.
    
    ## What's changed
    
    - Track the immediate history source copied into a forked thread through
    thread/session creation, including subagent and review turn metadata
    paths.
    - Include `forked_from_thread_id` in Codex turn metadata while
    preventing turn-scoped Responses API client metadata from overwriting
    Codex-owned lineage fields.
    - Add coverage for fork lineage in turn metadata and the app-server
    Responses API request path.
  • Respect resume cwd overrides for idle cached threads (#24528)
    Fixes #24186.
    
    ## Why
    When the TUI resumes a thread through the local app-server daemon with a
    selected workspace, `thread/resume` can hit an already-loaded but idle
    cached thread. That path previously rejoined the cached `CodexThread`,
    so cwd/config overrides in `ThreadResumeParams` were ignored and the
    resumed session kept using the old cwd.
    
    ## What changed
    App-server now treats a loaded-but-idle thread with no subscribers as a
    cache entry when resume overrides differ: it unloads that cached thread
    and lets the normal resume path rebuild it with the requested
    cwd/config. Threads that still have subscribers, or active runtime work,
    continue to rejoin the existing loaded thread so in-flight state remains
    observable.
    
    The existing thread teardown helper was generalized from
    archive-specific cleanup to shared unload cleanup for this path.
  • fix(remote-control): surface websocket task stalls (#24473)
    ## Why
    
    When the app-server remote-control websocket path stalls during
    connection setup or teardown, the existing logs do not show where the
    task stopped, and several awaits can keep the task from returning
    promptly. That makes offline or stale-host incidents hard to distinguish
    from expected shutdown or disable flow.
    
    Issue: N/A (internal incident investigation)
    
    ## What Changed
    
    Added structured lifecycle and status logging around remote-control
    enable/disable requests, websocket task startup and exit, connection
    cycles, enrollment context, and status/environment transitions.
    
    Bound websocket connect, transport-event forwarding, and
    connection-worker shutdown waits. On timeout, the code logs the stalled
    operation and stops or aborts workers so the loop can reconnect or exit
    instead of waiting indefinitely. Ping sends now also observe shutdown
    cancellation.
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • tui: keep inaccessible apps out of mentions (#24625)
    ## Summary
    
    Fix the TUI `$` app mention paths so App Directory rows that are not
    accessible are not treated as usable apps.
    
    This includes the core preservation fix from #24104, but expands it to
    the other app mention paths:
    
    - preserve app-server `is_accessible` flags when partial
    `app/list/updated` snapshots reach the TUI
    - require apps to be both accessible and enabled when resolving exact
    `$slug` mentions
    - require restored/stale `app://...` bindings to point at accessible,
    enabled apps before emitting structured app mentions
    - remove the now-unused `codex-chatgpt` dependency from `codex-tui`,
    which addresses the `cargo shear` failure seen on #24104
    
    ## Root Cause
    
    The app server already sends merged app snapshots with accessibility
    computed. The TUI handled app-server app list updates as partial app
    loads and re-ran the old accessible-app merge path. That path treated
    every notification row as accessible, so App Directory entries with
    `isAccessible=false` could appear in `$` suggestions.
    
    Regression source: #22914 routed app-list updates through the app server
    while reusing the old TUI partial-load handling. Related precursor:
    #14717 introduced the partial-load path, but #22914 made it user-visible
    for app-server updates.
    
    ## Issues
    
    Fixes #24145
    Fixes #24205
    Fixes #24319
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just argument-comment-lint -p codex-tui`
    - `just test -p codex-tui
    chatwidget::tests::popups_and_settings::apps_notification_update_excludes_inaccessible_apps_from_mentions
    chatwidget::tests::composer_submission::submit_user_message_ignores_inaccessible_app_mentions_from_bindings
    chatwidget::skills::tests::find_app_mentions_requires_accessible_enabled_apps_for_bound_paths
    chatwidget::skills::tests::find_app_mentions_requires_accessible_enabled_apps_for_slugs`
  • fix(tui): keep raw output above composer in zellij (#24593)
    ## Why
    
    Raw output mode intentionally sends logical source lines to the terminal
    without Codex-inserted wrapping so copied content retains its original
    line structure. In Zellij, soft-wrapped continuation rows from those raw
    lines are not confined by the inline history scroll region. When raw
    mode replays a long transcript, continuation rows can occupy the
    composer viewport and are overwritten on the following draw, leaving the
    transcript visibly truncated underneath the composer.
    
    This is specific to the combination of Zellij and raw terminal-wrapped
    history. Rich output and non-Zellij terminals should continue using the
    existing insertion behavior.
    
    Related context: #20819 introduced raw output mode, and #22214 removed
    the broad Zellij insertion workaround after the standard rich-output
    path no longer required it.
    
    | Before | After |
    |---|---|
    | <img width="1728" height="916" alt="image"
    src="https://github.com/user-attachments/assets/f85398a5-e930-46d9-bcfd-106a24c41466"
    /> | <img width="1723" height="912" alt="image"
    src="https://github.com/user-attachments/assets/5c62e16a-a6e5-4842-bcb2-eab163cda04c"
    /> |
    
    ## What Changed
    
    - Cache Zellij detection in `Tui` and select a dedicated insertion mode
    only for `HistoryLineWrapPolicy::Terminal` batches in Zellij.
    - For that guarded path, clear the existing viewport, append raw source
    lines through the terminal so its soft wrapping remains
    selection-friendly, and reserve empty viewport rows before redrawing the
    composer.
    - Add snapshot regressions for both an incremental soft-wrapped raw
    insert and an overflowing raw transcript replay that starts at the top
    of the cleared terminal.
    
    ## How to Test
    
    1. Start Codex inside Zellij with raw output enabled or toggle raw
    output after a multiline response is in history.
    2. Produce or replay output containing long logical lines, such as a
    fenced shell command with several wrapped lines.
    3. Confirm the wrapped history remains visible above the composer and
    the composer no longer overwrites the end of the response.
    4. Toggle back to rich output or run outside Zellij and confirm standard
    history rendering still behaves normally.
    
    Targeted tests run:
    
    - `just test -p codex-tui vt100_zellij_raw -- --nocapture`
    
    Additional validation notes:
    
    - `just test -p codex-tui` was attempted; the two new Zellij raw
    insertion tests passed, while two existing
    `app::tests::update_feature_flags_disabling_guardian_*` tests failed
    outside this history insertion path.
    - `just argument-comment-lint` was attempted but local Bazel analysis
    fails before reaching the changed source because the LLVM `compiler-rt`
    package is missing `include/sanitizer/*.h`. Modified literal callsites
    were inspected manually.
  • standalone websearch extension (#23823)
    ## Summary
    
    Add the extension-backed standalone `web.run` tool so Codex can call the
    standalone search endpoint through the `codex-api` search client and
    return its encrypted output to Responses.
    
    - gate the new tool behind `standalone_web_search`
    - install the extension in the app-server thread registry and hide
    hosted `web_search` when standalone search is enabled for OpenAI
    providers so the two paths stay mutually exclusive
    - build search context from persisted history using a small tail
    heuristic: previous user message, assistant text between the last two
    user turns capped at about 1k tokens, and current user message
    
    ## Test Plan
    
    - `cargo test -p codex-web-search-extension`
    - `cargo test -p codex-api`
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates`
  • Move memory state to a dedicated SQLite DB (#24591)
    ## Summary
    
    Generated memory rows and their stage-one/stage-two job state currently
    live in `state_5.sqlite` alongside thread metadata. That makes memory
    cleanup and regeneration share the main state schema even though those
    rows are memory-pipeline data and can be rebuilt independently from the
    durable thread records.
    
    This PR moves the memory-owned tables into a dedicated
    `memories_1.sqlite` runtime database while keeping thread metadata in
    `state_5.sqlite`.
    
    ## Changes
    
    - Adds a separate memories DB runtime, migrator, path helpers, telemetry
    kind, and Bazel compile data for `state/memory_migrations`.
    - Introduces `MemoryStore` behind `StateRuntime::memories()` and moves
    memory table/job operations onto that store.
    - Drops the old memory tables from the state DB and recreates their
    schema in `state/memory_migrations/0001_memories.sql`.
    - Updates memory startup, citation usage tracking, rollout pollution
    handling, `debug clear-memories`, and app-server `memory/reset` to
    operate through the memories DB.
    - Preserves cross-DB behavior by hydrating thread metadata from the
    state DB when selecting visible memory outputs and checking stage-one
    staleness.
    
    ## Verification
    
    - Added/updated `codex-state` tests for deleted-thread memory visibility
    and already-polluted phase-two enqueue behavior.
    - Updated `debug clear-memories`, app-server `memory/reset`, and
    memories startup tests to seed and assert memory rows through
    `memories_1.sqlite`.
  • fix: restore goal accounting after thread resume (#24626)
    ## Why
    
    Goal idle accounting is supposed to survive a thread resume. Previously,
    the resume hook restored the active goal state inline from the extension
    lifecycle contributor, which left the runtime handle without a reusable
    restoration path and made the behavior hard to cover directly. When a
    thread with an active goal was resumed, goal accounting could lose track
    of the active idle goal instead of continuing to accrue elapsed time.
    
    ## What changed
    
    - Moved thread-resume restoration into
    `GoalRuntimeHandle::restore_after_resume()` so the runtime owns
    rehydrating active goal accounting from persisted thread goal state.
    - Kept disabled goal runtimes as a no-op and preserved the existing
    warning path when persisted goal state cannot be loaded.
    - Added a backend regression test that seeds an active goal, resumes the
    thread, waits briefly, and verifies elapsed idle time is reflected on
    the next external goal mutation.
    
    ## Testing
    
    - Not run locally; this metadata update only rewrote the PR title/body.
  • fix(tui): avoid modifyOtherKeys for unknown tmux formats (#24371)
    ## Why
    
    Codex 0.131 started enabling tmux `modifyOtherKeys` mode 2 when the
    active tmux session reported `extended-keys-format csi-u`, and also when
    that format could not be queried. The fallback was meant to help
    compatible tmux panes enter extended-key mode, but it breaks iTerm2
    control-mode sessions on older tmux.
    
    Issue #23711 reproduces with:
    
    ```bash
    ssh -t ubuntu@192.168.68.149 'tmux -CC new -A -s main'
    ```
    
    On tmux 3.2a, `extended-keys-format` is not available. With mode 2
    enabled, `Ctrl-C` is delivered as `^[[27;5;99~` instead of the normal
    interrupt/control key path, so Codex does not handle it. Running with
    `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1` restores `Ctrl-C`, which
    points at keyboard mode setup rather than chat input routing.
    
    ## What Changed
    
    - Only request `modifyOtherKeys` mode 2 when tmux explicitly reports
    `extended-keys-format csi-u`.
    - Treat an unknown or unavailable tmux extended-key format as
    unsupported for this mode.
    - Update the keyboard mode unit coverage so `None` no longer opts into
    `modifyOtherKeys`.
    
    This preserves the explicit modern tmux `csi-u` path from #21943 while
    avoiding the unsafe fallback on older or unqueryable tmux setups.
    
    ## How to Test
    
    Regression path from #23711:
    
    1. Start iTerm2 tmux integration against an older tmux host:
       ```bash
       ssh -t ubuntu@192.168.68.149 'tmux -CC new -A -s main'
       ```
    2. Start patched Codex.
    3. Run `/keymap debug`, press a regular key, then press `Ctrl-C`.
    4. Confirm `Ctrl-C` closes the inspector and Codex remains responsive
    without `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1`.
    5. Confirm `Shift+Enter` still inserts a newline in the same session.
    
    Modern tmux compatibility path:
    
    1. Start an ordinary tmux 3.6a server with explicit `csi-u`:
       ```bash
       tmux -L codex-csiu -f /dev/null new-session -d -s repro
       tmux -L codex-csiu set-option -g extended-keys on
       tmux -L codex-csiu set-option -g extended-keys-format csi-u
       tmux -L codex-csiu attach -t repro
       ```
    2. Start patched Codex.
    3. From another terminal, confirm the Codex pane reports `mode=Ext 2`:
       ```bash
    tmux -L codex-csiu list-panes -a -F '#{pane_id} mode=#{pane_key_mode}
    cmd=#{pane_current_command}'
       ```
    4. Type `one`, press `Shift+Enter`, type `two`, and confirm the composer
    shows two lines without submitting.
    5. Press `Ctrl-C` and confirm Codex handles it normally.
    
    Targeted tests:
    
    - `./tools/argument-comment-lint/run.py -p codex-tui -- --lib`
    - `just test -p codex-tui` runs the new keyboard mode test successfully;
    the full run currently reports two unrelated guardian feature-flag test
    failures:
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
    -
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    
    No documentation update is needed.
  • Add goal extension telemetry parity (#24615)
    ## Why
    
    `core/src/goals.rs` already emits OTEL metrics for goal creation,
    resume, terminal transitions, token counts, and duration. As `/goal`
    moves into `ext/goal`, the extension needs to preserve that telemetry
    contract instead of only emitting app-visible `ThreadGoalUpdated`
    events.
    
    This keeps the existing `codex.goal.*` metric surface intact while goal
    lifecycle ownership shifts toward the extension.
    
    ## What changed
    
    - Added an extension-local `GoalMetrics` helper that records the
    existing `codex.goal.*` counters and histograms through `codex-otel`.
    - Threaded an optional `MetricsClient` through `install_with_backend`,
    `GoalExtension`, `GoalRuntimeHandle`, and `GoalToolExecutor`.
    - Emitted created, resumed, and terminal goal metrics from the extension
    paths that create goals, restore active goals on thread resume, account
    budget limits, complete or block goals, and handle external goal
    mutations.
    - Updated existing goal extension test setup callsites to pass `None`
    for metrics when instrumentation is not under test.
    
    ## Verification
    
    Not run locally.