Commit Graph

6799 Commits

  • fix: plugin bundle archive handling for upload and install (#23983)
    Move plugin tar.gz packing and unpacking into a shared core-plugins
    archive helper so uploaded bundles are decoded through the same tar
    handling used for installs. This removes duplicate archive logic,
    supports GNU long-name entries on extraction, and keeps size, traversal,
    link, and entry-type checks in one place.
  • code-mode: merge stored values by key (#24159)
    ## Summary
    
    Change code-mode stored value updates to merge writes by key instead of
    replacing the session's complete stored-value map after each cell
    completes.
    
    Previously, each cell received a snapshot of stored values and returned
    the complete resulting map. When multiple cells ran concurrently, a
    later completion could overwrite values written by another cell because
    it committed an older snapshot.
    
    This change moves stored-value ownership into `CodeModeService`:
    
    - Each runtime starts from the service's current stored values.
    - Runtime completion reports only keys written by that cell.
    - The service merges those writes into the current stored-value map on
    successful completion.
    - Core no longer replaces its stored-value state from a cell result.
    
    As a result, concurrently executing cells can update different stored
    keys without clobbering one another.
    
    The move into CodeModeService is motivated by a desire to have this
    lifetime tied to a new lifetime object on that side in a subsequent PR.
  • Default function tools into tool hooks (#23757)
    # Why
    
    `PreToolUse`, `PostToolUse`, and `updatedInput` coverage for local
    function tools currently depends on each handler remembering to wire up
    the hook contract itself. That makes coverage easy to miss as new
    function tools are added, even though most of them share the same basic
    shape: a model-facing function call with JSON arguments.
    
    # What
    
    This makes `CoreToolRuntime` provide the default hook contract for
    ordinary local function tools:
    
    - build generic `PreToolUse` and `PostToolUse` payloads from the
    function tool name and arguments
    - apply `updatedInput` rewrites back into function-tool arguments
    through the same default path
    - let tool outputs override the post-hook input or response when they
    have a more stable hook-facing contract
    
    The exceptions stay explicit:
    
    - hosted tools remain outside the generic local function path
    - code-mode `wait` and `write_stdin` opt out for now
    - `PostToolUse` feedback replaces only the model-visible response, so
    code mode keeps its typed tool result
    
    With the generic path in place, the MCP and extension-tool adapters no
    longer need their own duplicate pre/post hook plumbing. The new coverage
    exercises the registry default plus end-to-end local function behavior
    for pre-hook blocking, `updatedInput` rewriting, and post-hook context.
  • package: include zsh fork in Codex package (#23756)
    ## Why
    
    The package layout gives Codex a stable place for runtime helpers that
    should travel with the entrypoint. `shell_zsh_fork` still required users
    to configure `zsh_path` manually, even though we already publish
    prebuilt zsh fork artifacts.
    
    This PR builds on #24129 and uses the shared DotSlash artifact fetcher
    to include the zsh fork in Codex packages when a matching target
    artifact exists. Packaged Codex builds can then discover the bundled
    fork automatically; the user/profile `zsh_path` override is removed so
    the feature uses the package-managed artifact instead of a legacy path
    knob.
    
    ## What Changed
    
    - Added `scripts/codex_package/codex-zsh`, a checked-in DotSlash
    manifest for the current macOS arm64 and Linux zsh fork artifacts.
    - Taught `scripts/build_codex_package.py` to fetch the matching zsh fork
    artifact and install it at `codex-resources/zsh/bin/zsh` when available
    for the selected target.
    - Added package layout validation for the optional bundled zsh resource.
    - Added `InstallContext::bundled_zsh_path()` and
    `InstallContext::bundled_zsh_bin_dir()` for package-layout resource
    discovery.
    - Threaded the packaged zsh path through config loading as the runtime
    `zsh_path` for packaged installs, and removed the config/profile/CLI
    override path.
    - Kept the packaged default zsh override typed as `AbsolutePathBuf`
    until the existing runtime `Config::zsh_path` boundary.
    - Updated app-server zsh-fork integration tests to spawn
    `codex-app-server` from a temporary package layout with
    `codex-resources/zsh/bin/zsh`, matching the new packaged discovery path
    instead of setting `zsh_path` in config.
    - Switched package executable copying from metadata-preserving `copy2()`
    to `copyfile()` plus explicit executable bits, which avoids macOS
    file-flag failures when local smoke tests use system binaries as inputs.
    
    ## Testing
    
    To verify that the `zsh` executable from the Codex package is picked up
    correctly, first I ran:
    
    ```shell
    ./scripts/build_codex_package.py
    ```
    
    which created:
    
    ```
    /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/
    ```
    
    so then I ran:
    
    ```
    /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/bin/codex exec --enable shell_zsh_fork 'run `echo $0`'
    ```
    
    which reported the following, as expected:
    
    ```
    /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/codex-resources/zsh/bin/zsh
    ```
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23756).
    * #23768
    * __->__ #23756
  • fix(remote-control): cap reconnect backoff (#24164)
    ## Why
    
    Remote-control websocket reconnects currently use the shared exponential
    backoff helper without a local ceiling, so a long failure streak can
    stretch retries out indefinitely and leave the runtime behavior hard to
    inspect from logs.
    
    ## What Changed
    
    Cap the remote-control reconnect delay at 30 seconds, then reset the
    reconnect attempt counter once that capped delay is emitted so the next
    failure starts from the initial jittered delay again.
    
    The reconnect failure log now records the attempt number, chosen delay,
    and whether the cap triggered a reset, with a separate info log when the
    backoff counter is reset after the cap.
    
    ## Verification
    
    `just test -p codex-app-server-transport`
    
    Related issue: N/A
  • release: build macOS x64 zsh artifact (#24165)
    ## Why
    
    The zsh release workflow currently publishes macOS arm64 and Linux zsh
    fork artifacts, but no macOS x64 artifact. The Codex package builder
    therefore cannot include codex-resources/zsh/bin/zsh for
    x86_64-apple-darwin packages.
    
    ## What Changed
    
    - Added an x86_64-apple-darwin row to the macOS zsh release matrix.
    - Runs that row on macos-15-large, the Intel macOS runner appropriate
    for the native zsh build.
    - Added the matching macos-x86_64 platform to the zsh DotSlash publish
    config so the generated release manifest can reference the new tarball.
  • Display workspace usage limit error copy from response header (#24114)
    ## Why
    
    `openai/openai#947613` adds `X-Codex-Rate-Limit-Reached-Type` for Codex
    workspace credit-depletion and spend-cap responses. The CLI currently
    reads the adjacent promo header but otherwise renders generic
    usage-limit copy, so those responses do not explain the
    workspace-specific action the user needs to take.
    
    Backend dependency: https://github.com/openai/openai/pull/947613
    
    ## What Changed
    
    - Parse `X-Codex-Rate-Limit-Reached-Type` in the usage-limit error
    handling path alongside `x-codex-promo-message`.
    - Keep the header value parsing with the shared `RateLimitReachedType`
    enum.
    - Carry the parsed type on `UsageLimitReachedError` and render
    client-owned copy for the four workspace owner/member credit and
    spend-cap values.
    - Preserve existing promo and plan-based text for absent, generic, or
    unknown header values.
    - Keep the existing TUI workspace-owner nudge state path unchanged; the
    response header only selects the displayed error string.
    - Add focused display coverage for all specific type values and the
    generic fallback case.
    
    ## Test Plan
    
    - Added `usage_limit_reached_error_formats_rate_limit_reached_types`
    coverage.
    - Not run manually, per request; CI runs validation on the pushed
    commit.
  • [codex] Remove external client session reset plumbing (#24157)
    ## Why
    
    The turn loop no longer needs to decide when a `ModelClientSession`
    should reset its websocket state after compaction. That reset behavior
    belongs inside the model client, where the websocket cache and retry
    state are owned. The repo guidance now calls this out explicitly so
    future changes let the incremental request logic decide whether the
    previous request can be reused.
    
    ## What Changed
    
    - Removed the `reset_client_session` return value from pre-sampling and
    auto-compact helpers in `core/src/session/turn.rs`.
    - Changed compaction helpers to return `CodexResult<()>` so callers only
    handle success or failure.
    - Made `ModelClientSession::reset_websocket_session` private to
    `core/src/client.rs`, leaving it callable only from model-client
    internals.
    - Added `AGENTS.md` guidance not to call `reset_client_session`
    unnecessarily.
    
    ## Validation
    
    - `just test -p codex-core session::turn`
  • chore: add JSON schema policy fixture coverage (#24152)
    ## Why
    
    Before changing the Codex Bridge JSON schema policy, add integration
    coverage around real connector-like MCP tool schemas. The existing unit
    tests cover individual sanitizer behaviors, but they do not make it easy
    to see whether full fixture schemas keep model-visible guidance, prune
    only unreachable definitions, drop unsupported JSON Schema fields, and
    stay within the Responses API schema budget.
    
    ## What Changed
    
    - Added `tools/tests/json_schema_policy_fixtures.rs`, which converts MCP
    tool fixtures through `mcp_tool_to_responses_api_tool` and validates the
    resulting Responses tool parameters.
    - Added connector-style fixtures for Slack, Google Calendar, Google
    Drive, Notion, and Microsoft Outlook Email under
    `tools/tests/fixtures/json_schema_policy/`.
    - Added fixture assertions for preserved guidance, pruned definitions,
    expected field drops after `JsonSchema` conversion, marker count
    baselines, and dangling local `$ref` prevention.
    - Added a real oversized golden Notion `create_page` input schema
    fixture to exercise the compaction path that strips descriptions, drops
    root `$defs`, rewrites local refs, and fits the compacted schema under
    the budget.
  • [codex] Add image re-encoding benchmarks (#23935)
    ## Summary
    - add Divan benchmarks for prompt image re-encoding paths
    - wire the image benchmark smoke test into Rust CI workflows
    
    ## Why
    Image prompt handling includes re-encoding work that benefits from
    repeatable benchmark coverage so changes can be measured in CI and
    locally.
    
    This already helped identify a potential regression from changing compiler flags.
    
    ## Impact
    Developers can run and compare the new image re-encoding benchmarks, and
    CI exercises the benchmark target via the Rust benchmark smoke test.
  • [codex] Use TurnInput for session task input (#24151)
    ## Why
    
    The idea here is to erase the difference between initial and followup
    inputs to a turn. Followup inputs are already represented as TurnInput.
    
    Eventual goal is not to have explicit on task input at all and pull
    everything from input Q.
    
    ## What Changed
    
    - Changes `SessionTask::run` and the erased `AnySessionTask::run` path
    to accept `Vec<TurnInput>`.
    - Wraps user-submitted spawn input as `TurnInput::UserInput` at the
    session task start boundary.
    - Updates `run_turn` to record initial `TurnInput` using the same hook
    and recording path used for pending input.
    - Keeps review-specific conversion local to `ReviewTask`, where the
    sub-Codex one-shot API still expects `Vec<UserInput>`.
    - Moves the synthetic compact prompt into `CompactTask` and starts
    compact tasks with empty task input.
    
    ## Validation
    
    - `cargo check -p codex-core`
    - `just test -p codex-core -E
    'test(task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input)
    | test(queued_response_items_for_next_turn_move_into_next_active_turn) |
    test(steered_input_reopens_mailbox_delivery_for_current_turn)'`
  • package: factor DotSlash executable fetching (#24129)
    ## Why
    
    The package builder already fetches `rg` from a checked-in DotSlash
    manifest. The zsh packaging work needs the same
    fetch/cache/size-check/SHA-256/extract path for another manifest, but
    keeping that refactor inside the zsh PR makes the review harder to
    follow.
    
    This PR factors the existing `rg`-specific implementation into a
    reusable helper with no intended behavior change for `rg` packaging.
    
    ## What Changed
    
    - Added `scripts/codex_package/dotslash.py` for checked-in DotSlash
    manifest parsing, archive download, cache reuse, size validation,
    SHA-256 validation, and member extraction.
    - Updated `scripts/codex_package/ripgrep.py` to delegate to the shared
    helper.
    - Preserved the existing `rg` manifest path, cache key, destination
    filename, and executable-bit behavior.
    
    ## Testing
    
    - `python3 -m py_compile scripts/codex_package/dotslash.py
    scripts/codex_package/ripgrep.py scripts/codex_package/cli.py
    scripts/codex_package/layout.py scripts/codex_package/zsh.py`
    - `python3 -m unittest discover scripts/codex_package`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/24129).
    * #23768
    * #23756
    * __->__ #24129
  • [codex-analytics] split compaction v2 analytics implementation (#24146)
    ## What changed
    
    - Add a distinct `responses_compaction_v2` value for
    `CodexCompactionEvent.implementation`.
    - Emit that value from the remote compaction v2 path.
    - Keep local compaction as `responses` and legacy `/responses/compact`
    as `responses_compact`.
    
    ## Why
    
    Remote compaction v2 and local prompt-based compaction were both
    reported as `responses`, which made the analytics table collapse two
    different compaction mechanisms into one implementation bucket.
    
    ## Validation
    
    - `just fmt`
    - `just test -p codex-analytics`
    
    `just test -p codex-core` was started locally, but this PR is
    intentionally being pushed for CI to finish the remaining validation.
  • Add typed Images client to codex-api (#23989)
    ## Why
    
    Standalone image generation needs a typed `codex-api` client surface for
    the Codex image proxy routes before the harness and model-facing tool
    layers are wired in.
    
    ## What changed
    
    - Added `ImagesClient` support for JSON `images/generations` and
    `images/edits` requests.
    - Added typed request and response shapes for generation, JSON edit
    image URLs, image metadata, and base64 image outputs.
    - Kept generation model slugs open-ended while requiring the generation
    model field that the downstream endpoint expects.
    - Exported the new client and image types from `codex-api`.
    - Added coverage for generation and edit wire shapes, extra response
    metadata that the client ignores, and malformed image responses missing
    `data`.
    
    ## Validation
    
    - `cargo test -p codex-api`
    - `just fix -p codex-api`
    - `just fmt`
    - `git diff --check main`
  • Support OAuth options in codex mcp add (#24120)
    ## Summary
    - add `--oauth-client-id` and `--oauth-resource` options for streamable
    HTTP `codex mcp add` registrations
    - persist those options in MCP server config and use them during the
    immediate OAuth login flow
    - cover add-time serialization of both OAuth options in the CLI
    integration tests
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-cli`
    - `just fix -p codex-cli`
  • Add trace_id to TurnStartedEvent (#23980)
    ## Why
    [Recent PR](https://github.com/openai/codex/pull/22709) removed
    `trace_id` from `TurnContextItem`.
    
    ## What changed
    - Add to `TurnStartedEvent` so rollout consumers can correlate turns
    with telemetry traces.
    - Note that the branch name is out of date because I originally re-added
    to `TurnContextItem`, but we decided to move it to `TurnStartedEvent`.
    
    ## Verification
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core --lib
    regular_turn_emits_turn_started_without_waiting_for_startup_prewarm`
    - `cargo test -p codex-core --test all
    emits_warning_when_resumed_model_differs`
    - `cargo test -p codex-rollout`
    - `cargo test -p codex-state`
  • cli: support --profile for codex sandbox (#24110)
    ## Why
    
    `codex sandbox` now always runs the host sandbox backend, so it should
    accept the same profile selection mechanism as the rest of the runtime
    CLI surface. Without `--profile`, sandbox debugging can exercise only
    the default config stack unless users manually translate profile config
    into ad hoc `-c` overrides.
    
    Supporting `--profile` lets sandbox invocations load
    `$CODEX_HOME/<name>.config.toml`, including permission profile
    configuration, before resolving the sandbox policy for the command being
    run.
    
    ## What Changed
    
    - Added `--profile NAME` / `-p NAME` to the host-specific `codex
    sandbox` argument structs as `config_profile`.
    - Allowed root-level `codex --profile NAME sandbox ...` and made a
    sandbox-local `codex sandbox --profile NAME ...` override the root
    selection.
    - Threaded `LoaderOverrides` through sandbox config loading so selected
    config profile files participate in permission resolution before the
    legacy read-only fallback.
    - Documented the new sandbox flag in `codex-rs/README.md`.
    
    ## Verification
    
    - Added parser coverage for `codex sandbox --profile`.
    - Added sandbox config-loader coverage that verifies selected config
    profile loader overrides select the profile config rather than falling
    back to read-only.
    - Ran `cargo test -p codex-cli`.
  • fix(tui): restore Windows VT before TUI renders (#24082)
    ## Why
    
    Older Git for Windows versions can leave the Windows console output mode
    without virtual terminal processing after Codex runs git metadata
    commands in a repository. When the TUI later emits ANSI control
    sequences for redraws, restore, or image rendering, Windows Terminal can
    show raw escape bytes or leave the prompt/status area corrupted.
    
    This is a targeted mitigation for the repo-conditioned Windows rendering
    corruption reported in #23888 and related reports #23512 and #23628.
    Updating Git avoids the trigger for affected users, but Codex should
    also reassert the terminal mode before it writes TUI control sequences.
    
    | Before | After |
    |---|---|
    | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23 21"
    src="https://github.com/user-attachments/assets/3218c379-5f97-4c71-ab25-805c9d20578a"
    /> | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23
    58"
    src="https://github.com/user-attachments/assets/55ac72bb-37d0-400e-99bc-12dd5ea4092d"
    /> |
    
    
    ## What Changed
    
    - Re-enable Windows virtual terminal processing for stdout and stderr
    before TUI mode setup, restore, redraw, resume, and pet image render
    paths.
    - Treat invalid, null, or non-console handles as no-ops so redirected or
    non-console output is unaffected.
    - Keep the helper as a no-op on non-Windows platforms.
    
    ## How to Test
    
    1. On Windows Terminal with a Git 2.28.0 for Windows install, start
    Codex inside a valid Git repository.
    2. Start a new Codex CLI session.
    3. Confirm the prompt, working indicator, and bottom status line remain
    readable instead of showing raw ANSI escape sequences.
    4. Repeat outside a Git repository to confirm the ordinary non-repo
    startup path is unchanged.
    
    Targeted tests:
    - Not run locally; the behavior depends on Windows console mode APIs and
    the current worktree is on macOS.
  • docs: update README.md to mention curl-based installer (#24106)
    Now that users can install via `curl` (or `irm`), we should tell them
    about it so they no longer need to use `npm`!
    
    Note that on one Windows machine I tested on, when I ran:
    
    ```
    irm https://chatgpt.com/codex/install.ps1 | iex
    ```
    
    I got this error:
    
    ```
    iex : The property 'OSArchitecture' cannot be found on this object. Verify that the property exists.
    At line:1 char:45
    + irm https://chatgpt.com/codex/install.ps1 | iex
    +                                             ~~~
        + CategoryInfo          : NotSpecified: (:) [Invoke-Expression], PropertyNotFoundException
        + FullyQualifiedErrorId : PropertyNotFoundStrict,Microsoft.PowerShell.Commands.InvokeExpressionCommand
    ```
    
    so we'll recommend the following that works from both `cmd.exe` and
    PowerShell:
    
    ```
    powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
    ```
    
    This PR makes a slight update to `codex-rs/tui/src/update_action.rs` to
    match.
  • [codex] Use rolling files for Windows sandbox logs (#24117)
    ## Why
    
    Windows sandbox diagnostics currently append to a single `sandbox.log`
    under `CODEX_HOME/.sandbox`. That file never rolls over, which makes it
    hard to safely include sandbox diagnostics in future feedback reports
    without risking unbounded growth.
    
    ## What changed
    
    - Replaced direct append-open sandbox logging with
    `tracing_appender::rolling::RollingFileAppender`.
    - Configured sandbox logs to rotate daily using names like
    `sandbox.YYYY-MM-DD.log`.
    - Added a conservative `MAX_LOG_FILES` cap of 90 retained matching log
    files.
    - Routed the Windows sandbox setup helper through the same rolling
    writer.
    - Added helpers for resolving the current daily sandbox log path so
    future feedback upload work can use the same filename logic.
    - Updated tests and test diagnostics to read the dated daily log file.
    
    This intentionally does not include sandbox logs in `/feedback` yet;
    scrubbing and attachment behavior can happen in a follow-up.
    
    ## Testing
    
    - `cargo fmt -p codex-windows-sandbox`
    - `cargo check -p codex-windows-sandbox`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-windows-sandbox logging::tests`
    - `cargo clippy -p codex-windows-sandbox --all-targets -- -D warnings`
  • Add new enterprise requirement gate (#23736)
    Add new enterprise requirement gate.
    
    Validation:
    - `cargo test -p codex-config --lib`
    - `cargo test -p codex-app-server-protocol --lib`
    - `cargo test -p codex-tui --lib debug_config`
    - `cargo test -p codex-app-server --lib` *(fails: stack overflow in
    `in_process::tests::in_process_start_initializes_and_handles_typed_v2_request`;
    reproduces when run alone)*
  • app-server: drop legacy profile config surface (#24067)
    ## Why
    
    Legacy `[profiles.<name>]` config tables and the legacy `profile`
    selector are being retired in favor of profile files selected with
    `--profile <name>`. After #23886 removed the CLI-side legacy profile
    plumbing, the app-server config surface still exposed those fields and
    still carried conversion code for the old protocol shape.
    
    ## What changed
    
    - Remove `profile`, `profiles`, and `ProfileV2` from the app-server
    config protocol/schema output so `config/read` no longer returns legacy
    profile config.
    - Drop the old v1 `UserSavedConfig` profile conversion path from
    `config`.
    - Reject new app-server config writes under `profiles.*` with the same
    migration direction used for `profile`, while still allowing callers to
    clear existing legacy profile tables.
    - Refresh app-server config coverage and the experimental API README
    example around the remaining `Config` nesting path.
    
    ## Verification
    
    - Added config-manager coverage that `config/read` omits legacy profile
    config, `profiles.*` writes are rejected, and existing legacy profile
    tables can still be cleared.
    - Updated the v2 config RPC test to cover the rejected `profiles.*`
    batch-write path.
  • cli: infer host sandbox backend (#24102)
    ## Why
    
    `codex sandbox` previously required an OS subcommand like `linux`,
    `macos`, or `windows`, even though the command can only run the sandbox
    backend available on the current host. That made the CLI imply a
    cross-OS choice that does not exist.
    
    ## What changed
    
    - Collapse `codex sandbox <os>` into `codex sandbox [COMMAND]...` by
    wiring the `sandbox` parser directly to the host-specific backend args
    with `cfg`.
    - Keep the existing backend runners for Seatbelt, Linux sandbox, and
    Windows restricted token.
    - Rename the public Windows debug sandbox runner to
    `run_command_under_windows_sandbox` for clarity.
    - Update the Rust sandbox docs and related README references to describe
    host OS selection and avoid pointing readers at legacy `sandbox_mode`
    config.
    
    ## Arg0 compatibility
    
    The `codex-linux-sandbox` helper path is still handled before normal CLI
    parsing. `arg0_dispatch()` checks whether the executable basename is
    `codex-linux-sandbox` and directly calls
    `codex_linux_sandbox::run_main()`, so removing the `sandbox linux`
    parser branch does not affect the arg0 helper flow.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - `cargo test -p codex-arg0`
    - `just fix -p codex-cli`
  • tui: make codex-tui.log opt-in (#24081)
    ## Why
    
    The TUI currently creates a shared plaintext `codex-tui.log` under the
    default log directory. That append-only file can keep growing across
    runs even though the TUI already records diagnostics in bounded local
    stores.
    
    Make the plaintext file log an explicit troubleshooting choice instead
    of a default side effect.
    
    This is possible because logs are also stored in the DB with proper
    rotation
    
    ## What changed
    
    - Only install the TUI file logging layer when `log_dir` is explicitly
    set.
    - Remove the prior `codex-tui.log` at startup before an opt-in file
    layer is created.
    - Clarify the `log_dir` config/schema text and `docs/install.md` example
    so users opt in with `codex -c log_dir=...` when they need a plaintext
    log.
  • retry remote compaction v2 requests (#23951)
    ## Why
    
    Remote compaction v2 sends a normal `/responses` request with a
    compaction trigger. It should follow the retry semantics used by normal
    Responses streaming calls for transient stream/request failures, while
    keeping a smaller per-transport retry budget because compact attempts
    can run much longer than normal turns.
    
    ## What changed
    
    - Add a v2 compaction retry loop that uses `stream_max_retries`,
    matching normal Responses turn retry mechanics.
    - Cap the compact v2 retry budget at 2 retries per transport with
    `min(stream_max_retries, 2)`.
    - Retry retryable request-open and post-open stream collection failures
    through the same loop.
    - Use the existing 200ms exponential backoff and requested retry delay
    handling used by normal turn retries.
    - Emit the same `Reconnecting... n/max` stream-error notification
    pattern.
    - Fall back from WebSockets to HTTPS after the compact v2 stream retry
    budget is exhausted, then reset the retry counter for HTTPS.
    - Keep final remote-compaction failure logging after retries/fallback
    are exhausted.
    - Treat compact stream EOF before `response.completed` as a retryable
    stream failure.
    - Add compact v2 regression coverage with `request_max_retries = 0` and
    `stream_max_retries = 2`, covering both request-open failure and
    opened-stream EOF in one end-to-end test.
    
    ## Tests
    
    - `just fmt`
    - `cargo test -p codex-core remote_compact_v2`
    - `just fix -p codex-core`
  • Prefer just test over cargo test in docs (#23910)
    `cargo test` for the core and other crates fails on a fresh macOS
    checkout without the right stack size variable. This change encourages
    using the just test command that sets the environment up correctly.
    
    As a bonus, this should encourage agents to get more benefit out of
    nextest's parallel execution.
  • fix(app-server): fix optional bool annotations (#24099)
    `#[serde(default)]` wasn't sufficient for our generated TS types to
    reflect that clients didn't have to set them. We also need
    `skip_serializing_if = "std::ops::Not::not"`. This is already a rule in
    our agents.md file.
  • ci: Use codex produced v8 artifacts for release builds (#23934)
    Updates our build script to pull down the artifacts like we do in CI for
    building v8 into our targets.
    
    This changes the flow so that we now pre-install rusty v8 assets for all
    of our release targets from pre-built in workflow.
    Secondarily if running it locally we now optionally pull the assets down
    on python run assuming the user hasn't set the proper values, it then
    provides them.
    
    Sorry for the miss here.
  • fix: reject legacy profile selectors (#24059)
    ## Why
    
    `--profile` now selects `<name>.config.toml`, so the legacy `profile`
    selector should not be reintroduced through config write or MCP tool
    paths. A matching legacy selector in base user config also needs the
    same migration guard as a matching legacy `[profiles.<name>]` table so
    profile loading fails with one clear migration error instead of mixing
    the old and new profile models.
    
    ## What
    
    - reject non-null app-server config writes to the top-level legacy
    `profile` selector
    - make `--profile <name>` reject base user config that still selects the
    same legacy `profile = "<name>"` value, alongside the existing matching
    legacy profile-table guard
    - reject removed MCP `codex` tool fields such as `profile` by denying
    unknown tool-call parameters and exposing that restriction in the
    generated schema
    - add regression coverage for the app-server write paths, config loader
    guard, and MCP tool input/schema behavior
    
    ## Verification
    
    - targeted regression tests cover the new app-server, config loader, and
    MCP rejection paths
  • otel: drop legacy profile usage telemetry (#24061)
    ## Summary
    - drop the dead legacy profile usage metric and active-profile
    conversation-start fields
    - update role comments so they describe provider and service-tier
    preservation without legacy config-profile wording
    - pair the code cleanup with the file-backed profile docs update in
    openai/developers-website#1476
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-otel`
    - `cargo test -p codex-core` *(fails: existing stack overflow in
    `mcp_tool_call::tests::guardian_mode_mcp_denial_returns_rationale_message`)*
    - `cargo test -p codex-core --lib
    mcp_tool_call::tests::guardian_mode_mcp_denial_returns_rationale_message`
    *(fails with the same stack overflow)*
  • Avoid config snapshots in live agent subtree traversal (#24057)
    ## Why
    `/feedback` asks `ThreadManager` for the selected agent subtree before
    it uploads logs. The previous live subtree path reconstructed
    parent-child links by iterating every loaded thread and awaiting each
    thread config snapshot, so unrelated loaded-thread state could stall
    feedback subtree enumeration.
    
    The loaded-thread set already belongs to
    [`ThreadManagerState`](https://github.com/openai/codex/blob/50e6644c9425df2dcbfe52f65fd60bd7f15a8ea2/codex-rs/core/src/thread_manager.rs).
    Reading thread-spawn parents from the captured `CodexThread` session
    sources at that boundary keeps unload and resume behavior manager-owned
    while avoiding per-session config inspection.
    
    ## What Changed
    - expose parent-child thread-spawn edges for loaded, non-internal
    threads from `ThreadManagerState`
    - build the live child map from those edges while keeping agent metadata
    lookup and ordering in `AgentControl`
    - add regression coverage for live subtree enumeration when no state DB
    is available
    
    ## Validation
    - `git diff --check`
    - local Rust tests not run per request
  • config: remove legacy profile write paths (#24055)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2 and
    [#23886](https://github.com/openai/codex/pull/23886) removed CLI
    forwarding for the legacy profile-v1 path. Core and TUI config
    persistence still carried `active_profile` and
    `ConfigEditsBuilder::with_profile`, which let later writes continue
    targeting legacy `[profiles.<name>]` tables after profile selection
    moved to profile-v2 config files.
    
    ## What
    
    - Remove legacy profile routing from
    [`ConfigEditsBuilder`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/core/src/config/edit.rs#L1064-L1294),
    so core config edits no longer carry `with_profile` or infer
    `[profiles.*]` write targets from a `profile` key.
    - Drop `active_profile` plumbing from runtime `Config`, TUI
    startup/state, app-server config override forwarding, and Windows
    sandbox setup persistence.
    - Make app-server-backed TUI config edits use unscoped model,
    service-tier, feature, Auto-review, plan-mode, and Windows sandbox paths
    through
    [`tui/src/config_update.rs`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/tui/src/config_update.rs#L43-L112).
    - Update config edit coverage so legacy `profile` state stays untouched
    by direct model writes, and remove tests whose only contract was the
    deleted profile-scoped persistence path.
    
    ## Testing
    
    - Not run locally.
  • config: remove legacy profile v1 resolution (#24051)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved user-facing
    `--profile` selection onto profile v2, and
    [#23886](https://github.com/openai/codex/pull/23886) removed the old CLI
    `config_profile` override path. Core still had a second legacy path:
    `profile = "..."` could select `[profiles.*]` values while runtime
    config was built. Keeping that resolver alive preserves the old
    precedence model and profile-carrying surfaces even though profile
    selection now points at `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Reject legacy top-level `profile = "..."` config while loading runtime
    config, with an error that points callers at `--profile <name>` and
    `<name>.config.toml` in the [core load
    path](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2524-L2531).
    - Remove the remaining profile-v1 merge points from runtime config
    resolution, including features, permissions, model/provider selection,
    web search, Windows sandbox settings, TUI settings, role reloads, and
    OSS provider lookup.
    - Drop the leftover profile override surface from
    [`ConfigOverrides`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2118-L2148)
    and from the MCP server `codex` tool schema.
    - Prune profile-precedence tests that only exercised the removed
    resolver and replace them with rejection coverage for the legacy
    selector.
    
    ## Testing
    
    - Not run in this metadata pass.
    - Added
    [`legacy_profile_selection_is_rejected`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/config_tests.rs#L7942-L7965)
    coverage for the new runtime guard.
  • mcp: surface profile migration guidance under --profile (#23890)
    ## Why
    
    `codex --profile <name> mcp ...` should reach the same profile-v2
    migration guard as runtime commands. Otherwise legacy
    `[profiles.<name>]` users see the generic command-scope rejection
    instead of the existing guidance to move settings into
    `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Allow `codex mcp` through the `--profile` subcommand gate.
    - Pass profile loader overrides into the MCP entry point only to
    validate profile-v2 migration when a profile is present.
    - Keep MCP add/remove/list/get/login/logout behavior otherwise
    unchanged; this does not add profile-scoped MCP server management.
    - Cover the legacy profile migration error for `codex --profile work mcp
    list`.
    
    ## Testing
    
    - `cargo test -p codex-cli`
  • [codex] Enable Node env proxy for managed network proxy (#23905)
    ## Summary
    - set `NODE_USE_ENV_PROXY=1` when Codex applies managed network proxy
    environment overrides
    - keep the Node opt-in in the proxy environment key set used by
    shell/runtime env handling
    - cover the new env var in the focused network proxy env test
    
    ## Why
    Codex already sets HTTP proxy environment variables for child processes
    when the managed network proxy is active. Node's built-in network
    behavior needs the `NODE_USE_ENV_PROXY` opt-in to honor those env vars,
    so Node-based skill scripts can otherwise skip the managed proxy path
    and fail under restricted network access.
    
    ## Validation
    - `just fmt` in `codex-rs`
    - `cargo test -p codex-network-proxy` in `codex-rs`
  • Allow parallel MCP tool calls when annotated readOnly (#23750)
    ## Summary
    - Treat MCP tools with `readOnlyHint: true` as parallel-safe even when
    `supports_parallel_tool_calls` is unset or `false`.
    - Keep server-level `supports_parallel_tool_calls` as an additive
    override for non-read-only tools.
    - Add focused unit coverage for the MCP handler eligibility decision.
    - Update RMCP integration coverage to keep the serial baseline on a
    mutable tool, verify read-only concurrency without server opt-in, and
    preserve the server opt-in concurrency path separately.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-core --lib tools::handlers::mcp::tests::`
    - `cargo test -p codex-core --test all
    stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in`
    - `cargo test -p codex-core --test all
    stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently`
    - `cargo test -p codex-rmcp-client`
  • feat: best-effort compact large tool schemas (#23904)
    ## Why
    
    The `dev/cc/ref-def` branch preserves richer JSON Schema detail for
    connector tools, including `$defs` and nested shapes. That improves
    fidelity, but it pushes the largest connector schemas well past the
    intended tool-schema budget. This PR adds a best-effort compaction pass
    for unusually large tool input schemas so the p99 and max tails stay
    small while ordinary schemas are left alone.
    
    ## What Changed
    
    - Added best-effort large-schema compaction in
    `codex-rs/tools/src/json_schema.rs` after schema sanitization and
    definition pruning.
    - Compaction runs as a waterfall only while the compact JSON budget
    proxy is exceeded:
      1. Strip schema `description` metadata.
      2. Drop root `$defs` / `definitions`.
      3. Collapse deep nested complex schema objects to `{}`.
    - Kept top-level argument names and immediate schema shape where
    possible.
    
    ## Corpus Results
    
    Scope: 2,025 schemas under `golden_schemas`, all parsed successfully.
    Token count is `o200k_base` over compact JSON from
    `parse_tool_input_schema`.
    
    | Percentile | Before `origin/main` `4dbca61e20` | After branch
    `dev/cc/ref-def` `f9bf071758` | After this PR |
    |---|---:|---:|---:|
    | p0 | 9 | 9 | 9 |
    | p10 | 59 | 63 | 63 |
    | p25 | 81 | 86 | 86 |
    | p50 | 114 | 127 | 125 |
    | p75 | 174 | 205 | 202 |
    | p90 | 295 | 335 | 322 |
    | p95 | 391 | 526 | 422 |
    | p99 | 794 | 1,303 | 689 |
    | max | 2,836 | 3,337 | 887 |
    
    After this PR, `0 / 2,025` schemas are over 1k tokens.
    
    ### Compaction Savings
    
    These are cumulative waterfall stages over the same corpus. Later passes
    only run for schemas that are still over the compact JSON budget proxy.
    
    | Stage | Total tokens | Step savings | Schemas changed by step |
    |---|---:|---:|---:|
    | No compaction | 391,862 | - | - |
    | Strip schema `description` metadata | 350,961 | 40,901 | 66 |
    | Drop root `$defs` / `definitions` | 340,683 | 10,278 | 13 |
    | Collapse deep complex schemas to `{}` | 335,875 | 4,808 | 6 |
  • Expose conversation history to extension tools (#23963)
    ## Why
    
    Extension tools that need conversation context should be able to read it
    from the live tool invocation instead of reaching into thread
    persistence themselves.
    
    ## What changed
    
    - Add a `ConversationHistory` snapshot to extension `ToolCall`s and
    populate it from the current raw in-memory response history.
    - Expose all history items at this boundary so each extension can filter
    and bound the subset it needs before consuming or forwarding it.
    - Cover the adapter and registry dispatch paths and update existing
    extension tests that construct `ToolCall` literals.
    
    ## Test plan
    
    - `cargo test -p codex-tools`
    - `cargo test -p codex-extension-api`
    - `cargo test -p codex-goal-extension`
    - `cargo test -p codex-memories-extension`
    - `cargo test -p codex-core passes_turn_fields_to_extension_call`
    - `cargo test -p codex-core
    extension_tool_executors_are_model_visible_and_dispatchable`
  • feat: support local refs and defs in tool input schemas (#23357)
    # Why
    
    Some connector tool input schemas use local JSON Schema references and
    definition tables to avoid duplicating large nested shapes. Codex
    previously lowered these schemas into the supported subset in a way that
    could discard `$ref`-only schema objects and lose the corresponding
    definitions, which made non-strict tool registration less faithful than
    the original connector schema.
    
    This keeps the existing minimal-lowering policy: Codex still does not
    raw-pass through arbitrary JSON Schema, but it now preserves local
    reference structure that fits the Responses-compatible subset and prunes
    definition entries that cannot be reached by following `$ref`s from the
    root schema after sanitization, including refs found transitively inside
    other reachable definitions. The pruning matters because Responses
    parses definition tables even when entries are unused, so keeping dead
    definitions wastes prompt tokens.
    
    # What changed
    
    - Added `$ref`, `$defs`, and legacy `definitions` fields to the tool
    `JsonSchema` representation.
    - Updated `parse_tool_input_schema` lowering so `$ref`-only schema
    objects survive sanitization instead of becoming `{}`.
    - Sanitized definition tables recursively and dropped malformed
    definition tables so non-strict registration degrades gracefully.
    - Added reachability pruning for root definition tables by starting from
    refs outside definition tables, then following refs inside reachable
    definitions.
    - Added JSON Pointer decoding for local definition refs such as
    `#/$defs/Foo~1Bar`.
    
    # Verification
    ran local golden-schema probes against representative connector schemas
    to validate behavior on real generated schemas:
    
    | Golden schema | Before bytes | After bytes | `$defs` before -> after |
    `$ref` before -> after | Result |
    |---|---:|---:|---:|---:|---|
    | `google_calendar/create_space` | 7111 | 4526 | 7 -> 7 | 7 -> 7 | all
    definitions preserved because all are reachable |
    | `figma/apply_file_variable_changes` | 4609 | 999 | 8 -> 5 | 8 -> 5 |
    unused defs pruned after unsupported `oneOf` shapes lower away |
    | `snowflake/list_catalog_integrations` | 1380 | 404 | 3 -> 0 | 0 -> 0 |
    all defs pruned because none are referenced |
    | `dropbox/create_shared_link` | 8894 | 1836 | 14 -> 4 | 9 -> 4 | only
    defs reachable from the root schema after sanitization are retained,
    including transitively through other retained defs |
    
    Token increase across golden schema due to this change:
    <img width="817" height="366" alt="Screenshot 2026-05-19 at 1 47 04 PM"
    src="https://github.com/user-attachments/assets/d5c80fe9-da85-41e6-8ac7-a01d1e0b0f71"
    />
  • Fix auto-review permission profile override (#23956)
    ## Summary
    The auto-review runtime sync path was assigning a raw
    `PermissionProfile` into `runtime_permission_profile_override`, whose
    field now expects `RuntimePermissionProfileOverride`. That broke the TUI
    Bazel build.
    
    This changes the assignment to store
    `RuntimePermissionProfileOverride::from_config(&self.config)`, matching
    the other runtime override paths and preserving the active profile and
    network metadata with the permission profile.
  • Add Bedrock Mantle GovCloud region (#23860)
    ## Summary
    - Add us-gov-west-1 to the Bedrock Mantle supported region list
    - Cover the GovCloud endpoint URL in the existing base_url unit test
    
    ## Test
    - cargo test -p codex-model-provider
  • fix: Allow plugin skills to share plugin-level icon assets (#23776)
    Thread the plugin root through plugin skill loading so skill interface
    icons can reference shared plugin assets, such as ../../assets/logo.svg.
  • [3 of 4] tui: route feature and memory toggles through app server (#22915)
    ## Why
    Experimental feature toggles and memory settings can update several
    related config values in one interaction. Keeping those writes local in
    a remote TUI session is especially dangerous because the UI can diverge
    from the app-server config while also leaving behind partially stale
    supporting keys.
    
    This is **[3 of 4]** in a stacked series that moves TUI-owned config
    mutations onto app-server APIs.
    
    ## What changed
    - Routed feature flag persistence through app-server batch writes,
    including the supporting reviewer and permission updates used by
    guardian approval.
    - Routed Windows sandbox mode persistence and legacy Windows feature
    cleanup through app-server writes.
    - Routed memory settings through app-server batch writes and updated the
    TUI tests to exercise the embedded app-server path.
    
    ## Config keys affected
    - `features.<feature_key>`
    - `profiles.<profile>.features.<feature_key>`
    - `approval_policy`
    - `sandbox_mode`
    - `approvals_reviewer`
    - `windows.sandbox`
    - `features.experimental_windows_sandbox`
    - `features.elevated_windows_sandbox`
    - `features.enable_experimental_windows_sandbox`
    - Profile-scoped Windows legacy feature variants under
    `profiles.<profile>.features.*`
    - `memories.use_memories`
    - `memories.generate_memories`
    - Profile-scoped memory variants under `profiles.<profile>.memories.*`
    
    ## Suggested manual validation
    - Connect the TUI to a remote app server, toggle guardian approval on
    and off, and confirm the remote config updates
    `features.guardian_approval`, reviewer state, approval policy, and
    sandbox mode coherently.
    - Toggle a default-false experimental feature at the root level, disable
    it again, and confirm the key clears instead of lingering as an
    unnecessary explicit `false`.
    - Change memory settings and confirm the remote config updates both
    memory keys while the running TUI reflects the new state.
    - On Windows, switch sandbox mode through the TUI and confirm
    `windows.sandbox` is updated while the legacy Windows feature keys are
    cleared.
    
    ## Stack
    1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
    primary settings writes
    2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
    and skill enablement
    3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
    feature and memory toggles
    4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
    startup and onboarding bookkeeping
  • Add subagent identity to hook inputs (#22882)
    # What
    
    When a normal hook fires inside a thread-spawned subagent, Codex now
    includes these optional top-level fields in the hook input:
    
    - `agent_id`: the child thread id
    - `agent_type`: the subagent role
    
    Root-agent hook inputs omit these fields. `SubagentStart` and
    `SubagentStop` keep their existing required `agent_id` and `agent_type`
    fields because those events are inherently subagent-scoped.
    
    This does not change matcher behavior. Tool hooks still match on tool
    name, compact hooks still match on trigger, and `UserPromptSubmit` still
    ignores matchers. Only `SubagentStart` and `SubagentStop` match on
    `agent_type`.
  • fix(remote-control): retry after auth recovery (#23775)
    ## Why
    
    When remote control hits an auth failure such as a revoked or reused
    refresh token, the websocket loop falls into reconnect backoff. If the
    user fixes auth while that loop is sleeping, remote control can stay
    offline until the old retry timer expires because nothing wakes the loop
    or resets its exhausted auth recovery state.
    
    ## What Changed
    
    Added an auth-change watch on `AuthManager` for refresh-relevant cached
    auth updates.
    
    The remote-control websocket loop now subscribes to that signal, resets
    `UnauthorizedRecovery` and reconnect backoff when auth changes, and
    retries immediately instead of waiting for the previous delay.
    
    Updated the remote-control transport test to verify that reloading auth
    with the now-available account id wakes enrollment before the prior
    retry delay.
    
    ## Verification
    
    `cargo test -p codex-app-server-transport
    remote_control_waits_for_account_id_before_enrolling`
  • [codex] Make thread search case-insensitive (#23921)
    ## Summary
    - make rollout content search prefilter rollout files case-insensitively
    - keep the no-ripgrep fallback scan and visible snippet matcher aligned
    with that behavior
    - cover a lowercase `thread/search` query matching mixed-case
    conversation content
    
    ## Why
    The rollout-backed `thread/search` path used exact string matching in
    both its `rg` prefilter and semantic snippet generation. A content
    result could be missed solely because the query casing did not match the
    stored conversation text.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server thread_search_returns_content_matches`
    - `cargo test -p codex-rollout`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `cargo build -p codex-cli`
    - launched a local Electron dev instance with the rebuilt CLI binary
  • npm: remove legacy package artifact synthesis (#23836)
    ## Why
    
    `rust-release` now publishes `codex-package-<target>.tar.gz` as the
    canonical native package payload. npm staging should consume those
    archives directly instead of keeping legacy synthesis code that fetched
    `rg`, copied standalone binaries, and rebuilt an approximate package
    layout.
    
    That also means the package builder should not know the internal shape
    of `codex-package`. It should extract and copy the target payload
    wholesale so future layout changes stay localized to the archive
    producer.
    
    The release job stages `codex`, `codex-responses-api-proxy`, and
    `codex-sdk` together, so native artifact download should be filtered,
    observable, and shared across component installs. Since that native
    hydration is now only used by release staging, keeping a separate
    `install_native_deps.py` CLI adds an extra wrapper without a real
    caller.
    
    ## What Changed
    
    - Removed legacy `codex-package` synthesis and related compatibility
    flags from npm staging.
    - Folded the remaining native artifact hydration code into
    `scripts/stage_npm_packages.py` and deleted
    `codex-cli/scripts/install_native_deps.py`.
    - Made platform package staging copy the full extracted target directory
    instead of enumerating package entries.
    - Kept non-`codex-package` native components under their component
    directory name instead of using a legacy destination map.
    - Split native staging by component set while sharing one
    workflow-artifact cache across the invocation.
    - Changed workflow artifact download to select target artifacts by name,
    print sizes/progress, and reuse cached artifacts.
    - Removed the implicit `CI=true` default from `build_npm_package.py`;
    local CI-shaped runs should set that environment explicitly.
    - Kept `npm pack` cache/log output in its temporary directory so packing
    does not write to the user npm cache.
    
    ## Verification
    
    - `python3 -m py_compile scripts/stage_npm_packages.py
    codex-cli/scripts/build_npm_package.py`
    - `python3 -m unittest discover -s scripts/codex_package -p "test_*.py"`
    - `scripts/stage_npm_packages.py --help`
    - `codex-cli/scripts/build_npm_package.py --help`
    - Ran the release-shaped staging command from `rust-release.yml` against
    workflow run https://github.com/openai/codex/actions/runs/26240748758
    with `CI=true` set locally to match GitHub Actions:
    
    ```sh
    CI=true python3 ./scripts/stage_npm_packages.py \
      --release-version 0.133.0 \
      --workflow-url https://github.com/openai/codex/actions/runs/26240748758 \
      --package codex \
      --package codex-responses-api-proxy \
      --package codex-sdk
    ```
    
    That completed successfully, downloaded only the six target artifacts
    once, reused the cache for `codex-responses-api-proxy`, and produced all
    nine npm tarballs. Generated tarballs and staging/artifact temp dirs
    were cleaned afterward.
  • Remove plugin hooks feature flag (#22552)
    # Why
    
    This is a follow-up stacked on top of the `plugin_hooks` default-on
    change. Once we are comfortable making plugin hooks part of the normal
    plugin behavior, the separate feature flag stops buying us much and
    leaves extra branching/cache state behind.
    
    # What
    
    - remove the `PluginHooks` feature and generated config-schema entries
    - make plugin hook loading/listing follow plugin enablement directly
    - drop plugin-manager cache/state that only existed to distinguish
    hook-flag toggles
    - remove tests and fixtures that modeled `plugin_hooks = true/false`
  • [codex] Add rollout-backed thread content search (#23519)
    ## Summary
    - add experimental `thread/search` for local rollout-backed thread
    search using `rg` over JSONL rollouts
    - return search-specific result rows with optional previews instead of
    storing preview data on `StoredThread` or ordinary `Thread` responses
    - keep `thread/list` separate from full-content search and document the
    new app-server surface
    
    ## Testing
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_search_returns_content_and_title_matches -- --nocapture`