Commit Graph

6218 Commits

  • 2- Use string service tiers in session protocol (#20971)
    ## Summary
    - break service tier session/op/app-server protocol fields from the
    closed enum to string tier ids
    - send the service tier string directly through model requests, prewarm,
    compaction, memories, and TUI/app-server turn starts
    - regenerate app-server protocol JSON/TypeScript schemas, removing the
    standalone ServiceTier TS enum
    
    ## Verification
    - just fmt
    - cargo check -p codex-core -p codex-app-server -p codex-tui
    - just write-app-server-schema
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] fix builtin MCP Windows path test (#21350)
    ## Summary
    - make the builtin MCP config test derive the expected `--codex-home`
    argument from `AbsolutePathBuf`
    
    ## Why
    `AbsolutePathBuf::try_from("/tmp/codex-home")` is rendered as
    `D:\\tmp\\codex-home` on Windows, but the test asserted the Unix literal
    `"/tmp/codex-home"`. That made the Windows Bazel job fail even though
    the production code was behaving correctly.
    
    ## Impact
    This keeps the test cross-platform while preserving the same transport
    assertion on Unix and Windows.
    
    ## Validation
    - `cargo test -p codex-builtin-mcps`
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(app-server): move v2 sessionId onto Thread (#21336)
    ## Why
    
    `session_id` and `thread_id` are separate identities after #20437, but
    app-server only surfaced `sessionId` on the `thread/start`,
    `thread/resume`, and `thread/fork` response envelopes. Other
    thread-bearing surfaces such as `thread/list`, `thread/read`,
    `thread/started`, `thread/rollback`, `thread/metadata/update`, and
    `thread/unarchive` either lacked the grouping key or forced clients to
    special-case those three responses.
    
    Making `sessionId` part of the reusable `Thread` payload gives every v2
    API surface one place to expose session-tree identity.
    
    ## Mental model
      1. thread.sessionId lives on `Thread`
    2. It is a view/runtime identity for the current live session tree, not
    durable stored lineage metadata
    3. When app-server has a live loaded thread, it copies the real value
    from core’s session_configured.session_id
    4. When it only has stored/unloaded data, it falls back to
    thread.sessionId = thread.id
    
    ## What changed
    
    - Added `sessionId` to the v2
    [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109).
    - Removed the duplicate top-level `sessionId` fields from
    `thread/start`, `thread/resume`, and `thread/fork`; clients should now
    read `response.thread.sessionId`.
    - Populated `thread.sessionId` when building live thread responses,
    replaying loaded threads, and returning stored-thread summaries so the
    field is present across start, resume, fork, list, read, rollback,
    metadata-update, unarchive, and `thread/started` paths. See
    [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918)
    and
    [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719).
    - Preserved the stored-thread fallback: if a thread has not been loaded
    into a live session tree yet, `thread.sessionId` falls back to
    `thread.id`; once the thread is live again, the field reports the active
    session tree root.
    - Regenerated the JSON/TypeScript schemas and updated the app-server
    README examples to show
    [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310)
    on the thread object.
  • Move installation ID resolution out of core startup (#21182)
    ## Summary
    
    - resolve or inject the installation ID before core startup and pass it
    through `ThreadManager`, `CodexSpawnArgs`, and `Session` as a plain
    `String`
    - keep child sessions on the parent installation ID instead of
    rediscovering it inside core
    - propagate installation ID startup failures in `mcp-server` instead of
    panicking
    
    ## Why
    
    Core was still touching the filesystem on the session startup path to
    discover `installation_id`. This moves that work to the outer host
    boundary so core no longer depends on `codex_home` reads during session
    construction.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Propagate cache key and service tiers in compact (#21249)
    ## Why
    
    `/responses/compact` should preserve the request-affinity fields that
    apply to the active auth mode. ChatGPT-auth compact requests need the
    effective `service_tier`, and compact requests for every auth mode need
    the stable `prompt_cache_key`, so compaction does not quietly lose
    routing or cache behavior that normal sampling already has.
    
    This follows the request-parity direction from #20719, but keeps the net
    change focused on the compact payload fields needed here.
    
    ## What changed
    
    - Add `service_tier` and `prompt_cache_key` to the compact endpoint
    input payload.
    - Build the remote compact payload from the existing responses request
    builder output so `Fast` still maps to `priority` when compact sends a
    service tier.
    - Pass the turn service tier into remote compaction, but only include it
    in compact payloads for ChatGPT-backed auth.
    - Keep `prompt_cache_key` on compact payloads for all auth modes.
    - Add request-body diff snapshot coverage in
    `core/tests/suite/compact_remote.rs` for:
    - API-key auth reusing `prompt_cache_key` while omitting `service_tier`
    even when `Fast` is configured.
      - ChatGPT auth reusing both `service_tier` and `prompt_cache_key`.
    - Drive the snapshot coverage through five varied turns: plain text,
    multi-part text, tool-call continuation, image+text input, local-shell
    continuation, and final-turn reasoning output.
    
    ## Verification
    
    - Added insta snapshots for compact request-body parity against the last
    normal `/responses` request after five varied turns.
    - Not run locally per repo guidance; relying on GitHub CI for test
    execution.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: return session ID from thread/fork (#21332)
    ## Why
    
    `thread/start` and `thread/resume` already return `sessionId`, but
    `thread/fork` only returned the new thread. That left clients to infer
    the forked thread's session identity from `thread.id`, which kept the
    new `session_id` / `thread_id` split implicit at one lifecycle boundary.
    Follow-up to #20437.
    
    ## What changed
    
    - Add `sessionId` to `ThreadForkResponse`.
    - Populate it from the forked session configuration.
    - Regenerate the v2 JSON/TypeScript schema fixtures and update the
    app-server docs/example.
    - Extend the fork integration test to assert the returned `sessionId`.
    
    ## Verification
    
    - Added coverage in `thread_fork_creates_new_thread_and_emits_started`
    for the new response field.
  • feat: include thread ID in MCP turn metadata (#21329)
    ## Why
    
    MCP tool calls already include `session_id` in `x-codex-turn-metadata`,
    but descendant threads intentionally share that value with the root
    thread. Consumers that need to correlate work at the concrete thread
    level also need the current `thread_id`.
    
    ## What changed
    
    - add `thread_id` to `x-codex-turn-metadata` while preserving
    `session_id` as the shared session identity
    - thread the two identities separately through normal turns and spawned
    review threads
    - add regression coverage for resumed sessions, reserved metadata
    fields, and deferred MCP tool calls
    
    ## Verification
    
    - added focused coverage in `core/src/session/tests.rs`,
    `core/src/turn_metadata_tests.rs`, and `core/tests/suite/search_tool.rs`
  • test: isolate app-server-client in-process test state (#21328)
    ## Why
    
    The in-process `app-server-client` tests were still building their
    configs from the ambient `codex_home` and letting the embedded app
    server create its own state DB when `state_db` was absent. That matters
    because in-process startup falls back to
    `init_state_db_from_config(...)` in that case, so tests can otherwise
    share persisted state instead of getting isolated fixtures:
    [`app-server/src/in_process.rs`](https://github.com/openai/codex/blob/a98623511ba433154ec811fc63091617f5945438/codex-rs/app-server/src/in_process.rs#L368-L373).
    
    ## What changed
    
    - Give each in-process test client its own temporary `codex_home`.
    - Initialize the matching state DB from that per-client config and pass
    it into the client explicitly.
    - Keep the temp directory alive for the lifetime of the test client
    through a small `TestClient` wrapper.
    - Add `tempfile` as a dev dependency for the new harness.
    
    The updated setup lives in
    [`app-server-client/src/lib.rs`](https://github.com/openai/codex/blob/35c1133d45d10931914dbb88a1246a195d025ff6/codex-rs/app-server-client/src/lib.rs#L982-L1055).
    
    ## Testing
    
    - Existing `codex-app-server-client` tests continue to exercise the
    updated in-process client path through the isolated helper.
  • feat: add session_id (#20437)
    ## Summary
    
    Related to
    https://openai.slack.com/archives/C095U48JNL9/p1777537279707449
    TLDR:
    We update the meaning of session ids and thread ids:
    * thread_id stays as now
    * session_id become a shared id between every thread under a /root
    thread (i.e. every sub-agent share the same session id)
    
    This PR introduces an explicit `SessionId` and threads it through the
    protocol/client boundary so `session_id` and `thread_id` can diverge
    when they need to, while preserving compatibility for older serialized
    `session_configured` events.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Support Codex Apps auth elicitations (#19193)
    ## Summary
    
    - request URL-mode MCP elicitations when Codex Apps tool calls fail with
    connector auth metadata
    - route Codex Apps auth URL elicitations into the TUI app-link flow
    
    ## Test plan
    
    - `just fmt`
    - `cargo test -p codex-core mcp_tool_call::tests`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-tui bottom_pane::app_link_view::tests`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp`
    - `just fix -p codex-tui`
    
    Also attempted broader local runs:
    
    - `cargo test -p codex-core` fails in unrelated
    config/request-permission/proxy-sensitive tests under the current Codex
    Desktop environment.
    - `cargo test -p codex-tui` fails in unrelated status
    snapshots/trust-default tests because the ambient environment renders
    workspace-write/network permission defaults.
  • release: bundle bwrap with Linux codex DotSlash artifact (#21312)
    ## Why
    
    #21255 changed the Linux sandbox fallback so Codex can use a bundled
    `codex-resources/bwrap` executable when no suitable system `bwrap` is
    available. That lookup is relative to the native Codex executable
    returned by
    `std::env::current_exe()`, as implemented in
    [`bundled_bwrap.rs`](https://github.com/openai/codex/blob/9766d3d51cec885114b6d6c53a02e9efbaf87171/codex-rs/linux-sandbox/src/bundled_bwrap.rs#L83-L93).
    
    The release already publishes a separate `bwrap` DotSlash output, but
    the Linux `codex` DotSlash output still pointed at a single-binary
    `.zst` payload. Running the `codex` DotSlash manifest only materializes
    the native `codex` executable; it does not also create sibling files
    from the separate `bwrap` manifest. The fallback path therefore needs
    the Linux `codex` DotSlash artifact itself to include the real `bwrap`
    executable at `codex-resources/bwrap`.
    
    ## What changed
    
    - stage a Linux primary `codex-<target>-bundle.tar.zst` release artifact
    containing `codex` and `codex-resources/bwrap`
    - point the Linux `codex` DotSlash outputs at that bundle tarball
    - leave the standalone `bwrap` DotSlash output in place for consumers
    that want to fetch `bwrap` directly
    
    ## Verification
    
    - `jq . .github/dotslash-config.json`
    - Ruby YAML parse of `.github/workflows/rust-release.yml`
  • fix(bwrap): emit libcap after standalone archive (#21285)
    ## Why
    
    #21255 added the standalone `codex-bwrap` binary. In the Cargo build,
    [`pkg_config::probe("libcap")`](https://github.com/openai/codex/blob/a736cb55a2bce57b4c8e5a4fe56f70c2b2ad892b/codex-rs/bwrap/build.rs#L37-L39)
    emits `-lcap` before
    [`cc::Build::compile("standalone_bwrap")`](https://github.com/openai/codex/blob/a736cb55a2bce57b4c8e5a4fe56f70c2b2ad892b/codex-rs/bwrap/build.rs#L50-L67)
    adds the static bwrap archive. The Linux musl link then sees `-lcap
    -lstandalone_bwrap`; because static archives are resolved left-to-right,
    `cap_from_name` is still undefined once `standalone_bwrap` introduces
    that reference.
    
    The musl setup already builds `libcap.a` and exposes it through
    [`libcap.pc`](https://github.com/openai/codex/blob/a736cb55a2bce57b4c8e5a4fe56f70c2b2ad892b/.github/scripts/install-musl-build-tools.sh#L78-L88),
    so the failure is link ordering rather than a missing dependency.
    
    ## What changed
    
    - probe `libcap` with `cargo_metadata(false)` so `pkg-config` does not
    emit its link flags early
    - emit the discovered `libcap` search paths and libraries after
    `standalone_bwrap` is compiled, preserving the needed static-link order
    
    ## Verification
    
    - `cargo test -p codex-bwrap`
    - `cargo clippy -p codex-bwrap --all-targets`
    
    The affected Linux musl release link is exercised by CI, which is the
    path this fix targets.
  • [mcp] Return Accept early per feedback. (#21277)
    - [x] Return Accept early when auto_deny is enabled per feedback.
  • Preserve session MCP config on refresh (#21055)
    # Overview
    MCP refreshes were rebuilding active threads from fresh disk-backed
    config only, which dropped thread-start session overlays such as
    app-injected MCP servers. This keeps refreshes current with disk config
    while preserving the thread-local config that only the active thread
    knows about.
    
    # Changes
    - Rebuild refreshed config per active thread using that thread's current
    `cwd`, rather than fanning out one app-server config to every thread.
    - Preserve each thread's `SessionFlags` layer while replacing reloadable
    config layers with freshly loaded config, then derive the MCP refresh
    payload from the rebuilt result.
    - Move MCP refresh orchestration into app-server so manual refreshes
    fail loudly while background refreshes remain best-effort, and route
    plugin-triggered refreshes through the same per-thread reload path.
    - Add regression coverage for session overlays, fresh project config,
    plugin-derived MCP config, current requirements, and strict vs
    best-effort refresh behavior.
    
    # Verification
    - Passed focused Rust coverage for the thread-config rebuild behavior
    and deferred MCP refresh flow, plus `cargo test -p codex-app-server
    --lib`.
    - Verified end to end in the Codex dev app against the locally built
    CLI: registered an MCP via thread config, verified that it could be used
    successfully before refresh, manually triggered MCP refresh, and
    verified that it continued to be available afterward.
  • app-server: align dynamic tool identifiers with Responses API (#20724)
    ## Why
    
    Codex currently accepts dynamic tool names and namespaces that the
    upstream Responses function-tool path does not actually support. In
    practice, that means app-server can register a dynamic tool successfully
    and only discover later that the LLM-facing tool contract will reject or
    mishandle it.
    
    This PR tightens the app-server-side dynamic tool contract to match the
    Responses API before we stack dynamic tool hook support on top of it.
    
    ## What changed
    
    - validate dynamic tool `name` against the Responses function-tool
    identifier contract: `^[a-zA-Z0-9_-]+$`, length `1..128`
    - validate dynamic tool `namespace` the same way, with the Responses
    namespace length limit `1..64`
    - reject namespaces that collide with the always-reserved Responses
    runtime namespaces such as `functions`, `multi_tool_use`, `file_search`,
    `web`, `browser`, `image_gen`, `computer`, `container`, `terminal`,
    `python`, `python_user_visible`, `api_tool`, `tool_search`, and
    `submodel_delegator`
    - escape invalid identifiers in error messages so control characters do
    not spill raw into logs or client-visible error text
    - document the tightened dynamic tool identifier contract in
    `codex-rs/app-server/README.md`
    - add both unit coverage for the validator and an app-server integration
    test that rejects a `thread/start` request with Responses-incompatible
    dynamic tool identifiers
    
    ## Verification
    
    - `cargo test -p codex-app-server validate_dynamic_tools_`
    - `cargo test -p codex-app-server --test all
    thread_start_rejects_dynamic_tools_not_supported_by_responses`
  • feat: Add plugin share access controls (#21124)
    Extends `plugin/share/save` to accept optional discoverability and
    shareTargets while uploading plugin contents, and adds
    `plugin/share/updateTargets` for share-only target updates without
    re-uploading.
  • [codex-analytics] rework thread_source for thread analytics (#20949)
    ## Summary
    - make `thread_source` an explicit optional thread-level field on
    `thread/start`, `thread/fork`, and returned thread payloads
    - persist `thread_source` in rollout/session metadata so resumed live
    threads retain the original value
    - replace the old best-effort `session_source` -> `thread_source`
    mapping with an explicit caller-supplied analytics classification
    
    ## Why
    Before this change, analytics `thread_source` was populated by a
    best-effort mapping from `session_source`. `session_source` describes
    the runtime/client surface, not the actual thread-level origin, so that
    projection was not accurate enough to distinguish cases such as `user`,
    `subagent`, `memory_consolidation`, and future thread origins reliably.
    
    Making `thread_source` explicit keeps one thread-level analytics field
    while letting callers provide the real classification directly instead
    of recovering it indirectly from `session_source`.
    
    ## Impact
    For new analytics events, `thread_source` now reflects the explicit
    thread-level classification supplied by the caller rather than an
    inferred value derived from `session_source`. Existing protocol fields
    remain optional; callers that omit `threadSource` now produce `null`
    instead of a best-effort inferred value.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo test -p codex-analytics -p codex-core -p
    codex-app-server-protocol --no-run`
    - `cargo test -p codex-app-server-protocol
    generated_ts_optional_nullable_fields_only_in_params`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `cargo test -p codex-core
    resume_stopped_thread_from_rollout_preserves_thread_source`
  • Expose plugin manifest keywords in app server (#21271)
    ## Summary
    - Add plugin manifest keywords to core plugin marketplace/detail models
    - Expose keywords on app-server v2 PluginSummary and generated
    schema/types
    - Populate keywords in plugin/list and plugin/read responses for local
    plugins
    
    Depends on https://github.com/openai/openai/pull/891087
    
    ## Validation
    - just fmt
    - just write-app-server-schema
    - cargo test -p codex-app-server-protocol
    - cargo test -p codex-core-plugins
    - cargo test -p codex-app-server
    plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_load
    - cargo test -p codex-app-server
    plugin_read_returns_plugin_details_with_bundle_contents
  • [codex] Remove legacy ListSkills op (#21282)
    ## Why
    
    `skills/list` is already exposed through app-server v2 and covered by
    the app-server test suite. Keeping the separate core `Op::ListSkills`
    path leaves a duplicate legacy protocol surface that no longer needs to
    be maintained.
    
    ## What Changed
    
    - Removed `Op::ListSkills` and `EventMsg::ListSkillsResponse` from the
    core protocol.
    - Deleted the corresponding core session handler and stale core
    integration tests.
    - Removed rollout/MCP ignore branches and protocol v1 docs references
    for the deleted event/op.
    - Left app-server `skills/list` and its existing coverage intact.
    
    ## Validation
    
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core --test all suite::skills`
    - `cargo check -p codex-mcp-server -p codex-rollout -p
    codex-rollout-trace`
    - `just fix -p codex-core`
  • [codex] Remove unused ListModels op (#21276)
    ## Why
    
    The core protocol still exposed a `ListModels` submission op even though
    no client sends it and the core submission loop treated it as an ignored
    unknown op. Keeping the dead variant made the protocol surface look
    supported while the active model listing API is the app-server
    `model/list` JSON-RPC request.
    
    ## What Changed
    
    - Removed the unused `Op::ListModels` variant from `codex-rs/protocol`.
    - Removed its `Op::kind()` mapping.
    
    The existing app-server `model/list` endpoint is unchanged.
    
    ## Verification
    
    - `cargo test -p codex-protocol`
  • Share Git safe-command logic on Windows (#21275)
    ## Why
    
    BUGB-15601 showed that the Windows safe-command path had drifted from
    the generic Git classifier. The Windows-specific Git parser could
    classify a PowerShell-wrapped `git` command as safe as soon as it found
    a safelisted subcommand, without applying the generic checks for unsafe
    subcommand options such as `--output`, `--ext-diff`, `--textconv`,
    `--paginate`, or `cat-file --filters`.
    
    The generic classifier already models the Git command boundary and the
    read-only argument checks more carefully, so Windows should reuse that
    logic instead of maintaining a smaller parallel parser.
    
    ## What Changed
    
    - Extracted the existing generic Git classification logic into
    `is_safe_git_command`.
    - Updated `windows_safe_commands.rs` to call that shared helper for
    parsed PowerShell `git` commands.
    - Removed the Windows-only Git subcommand safelist, including the
    `cat-file` allowance that was part of the reported bypass.
    - Added a Windows regression test that keeps PowerShell-wrapped Git
    commands with side-effecting options classified unsafe.
    - Made the full-path PowerShell test discover the installed PowerShell
    executable instead of depending on one hard-coded `pwsh.exe` path.
    
    ## Verification
    
    - `cargo test -p codex-shell-command
    rejects_git_subcommand_options_with_side_effects`
    - `cargo test -p codex-shell-command
    git_global_override_flags_are_not_safe`
    - `cargo test -p codex-shell-command
    windows_powershell_full_path_is_safe -- --nocapture`
    
    Co-authored-by: Codex <codex@openai.com>
  • Add model and reasoning effort to MCP turn metadata (#21219)
    ## Why
    - Similar change as https://github.com/openai/codex/pull/19473.
    - Without change: MCP tool calls receive
    `_meta["x-codex-turn-metadata"]` with `session_id`, `turn_id`, and
    `turn_started_at_unix_ms`.
    - Issue: MCP servers may want the model and reasoning effort to better
    understand tool-call behavior and latency relative to turn start.
    
    ## What Changed
    - With change: MCP turn metadata now includes `model` and
    `reasoning_effort`, propagated in `_meta["x-codex-turn-metadata"]`.
    - Normal `/responses` turn metadata headers are unchanged.
    
    ## Verification
    - `codex-rs/core/src/mcp_tool_call_tests.rs`
    - `codex-rs/core/src/turn_metadata_tests.rs`
    - `codex-rs/core/tests/suite/search_tool.rs`
  • [codex] Move thread naming to app server (#21260)
    ## Why
    
    Thread names are app-server metadata now, backed by the thread store and
    sqlite state database. Keeping a core `SetThreadName` op plus a rollout
    `thread_name_updated` event made rename persistence live in the wrong
    layer and required historical replay support for an event that new
    app-server flows should not write.
    
    ## What changed
    
    - Removed `Op::SetThreadName` and `EventMsg::ThreadNameUpdated` from the
    core protocol and deleted the core handler path that appended rename
    events to rollouts.
    - Updated app-server `thread/name/set` so both loaded and unloaded
    threads write through thread-store metadata and app-server emits
    `thread/name/updated` notifications.
    - Updated local thread-store name metadata updates to write sqlite title
    metadata and the legacy thread-name index without appending rollout
    events.
    - Removed state extraction and rollout handling for the deleted
    thread-name event.
    
    ## Validation
    
    - `cargo test -p codex-app-server thread_name_updated_broadcasts`
    - `cargo test -p codex-app-server
    thread_name_set_is_reflected_in_read_list_and_resume`
    - `cargo test -p codex-thread-store
    update_thread_metadata_sets_name_on_active_rollout_and_indexes_name`
    - `cargo test -p codex-state`
    - `cargo check -p codex-mcp-server -p codex-rollout-trace`
    - `just fix -p codex-app-server -p codex-thread-store -p codex-state -p
    codex-mcp-server -p codex-rollout-trace`
    
    ## Docs
    
    No external documentation update is expected for this internal ownership
    change.
  • release: publish standalone bwrap artifacts (#21256)
    **Summary**
    - Build Linux `bwrap` before the main release binaries.
    - Export the release `bwrap` SHA-256 as `CODEX_BWRAP_SHA256` so the
    Codex binary can verify the bundled fallback.
    - Sign, stage, and upload `bwrap` alongside the primary Linux release
    artifacts.
    
    **Verification**
    - YAML parse check for `.github/workflows/rust-release.yml`
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21256).
    * #21257
    * __->__ #21256
  • linux-sandbox: use standalone bundled bwrap (#21255)
    **Summary**
    - Add `codex-bwrap`, a standalone `bwrap` binary built from the existing
    vendored bubblewrap sources.
    - Remove the linked vendored bwrap path from `codex-linux-sandbox`;
    runtime now prefers system `bwrap` and falls back to bundled
    `codex-resources/bwrap`.
    - Add bundled SHA-256 verification with missing/all-zero digest as the
    dev-mode skip value, then exec the verified file through
    `/proc/self/fd`.
    - Keep `launcher.rs` focused on choosing and dispatching the preferred
    launcher. Bundled lookup, digest verification, and bundled exec now live
    in `linux-sandbox/src/bundled_bwrap.rs`; Bazel runfiles lookup lives in
    `linux-sandbox/src/bazel_bwrap.rs`; shared argv/fd exec helpers live in
    `linux-sandbox/src/exec_util.rs`.
    - Teach Bazel tests to surface the Bazel-built `//codex-rs/bwrap:bwrap`
    through `CARGO_BIN_EXE_bwrap`; `codex-linux-sandbox` only honors that
    fallback in debug Bazel runfiles environments so release/user runtime
    lookup stays tied to `codex-resources/bwrap`.
    - Allow `codex-exec-server` filesystem helpers to preserve just the
    Bazel bwrap/runfiles variables they need in debug Bazel builds, since
    those helpers intentionally rebuild a small environment before spawning
    `codex-linux-sandbox`.
    - Verify the Bazel bwrap target in Linux release CI with a build-only
    check. Running `bwrap --version` is too strong for GitHub runners
    because bubblewrap still attempts namespace setup there.
    
    **Verification**
    - Latest update: `cargo test -p codex-linux-sandbox`
    - Latest update: `just fix -p codex-linux-sandbox`
    - `cargo check --target x86_64-unknown-linux-gnu -p codex-linux-sandbox`
    could not run locally because this macOS machine does not have
    `x86_64-linux-gnu-gcc`; GitHub Linux Bazel CI is expected to cover the
    Linux-only modules.
    - Earlier in this PR: `cargo test -p codex-bwrap`
    - Earlier in this PR: `cargo test -p codex-exec-server`
    - Earlier in this PR: `cargo check --release -p codex-exec-server`
    - Earlier in this PR: `just fix -p codex-linux-sandbox -p
    codex-exec-server`
    - Earlier in this PR: `bazel test --nobuild
    //codex-rs/linux-sandbox:linux-sandbox-all-test
    //codex-rs/core:core-all-test
    //codex-rs/exec-server:exec-server-file_system-test
    //codex-rs/app-server:app-server-all-test` (analysis completed; Bazel
    then refuses to run tests under `--nobuild`)
    - Earlier in this PR: `bazel build --nobuild //codex-rs/bwrap:bwrap`
    - Prior to this update: `just bazel-lock-update`, `just
    bazel-lock-check`, and YAML parse check for
    `.github/workflows/bazel.yml`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21255).
    * #21257
    * #21256
    * __->__ #21255
  • ci: trigger rusty-v8 releases from tags (#21259)
    Swap to tag based releasing and allow tags of type `rusty-v8-v*.*.*`
  • chore(app-server-protocol): split v2 API definitions into modules (#21251)
    ## Why
    
    `codex-rs/app-server-protocol/src/protocol/v2.rs` had grown into a
    single ~12k-line definition file for the entire app-server v2 API.
    
    This is purely a mechanical refactor to break up the monolithic `v2.rs`
    file that contains all app-server API v2 types into more modular files,
    grouped by resource (e.g. account, thread, turn, etc.).
    
    `just write-app-server-schema` shows no real changes, so we can be sure
    that this is purely an internal organizational change.
    
    ## What changed
    
    - Replaced the monolithic `protocol/v2.rs` with a `protocol/v2/` module
    tree and a small `mod.rs` that only declares and reexports modules.
    - Grouped v2 API definitions by conceptual owner, including `account`,
    `apps`, `collaboration_mode`, `command_exec`, `config`, `device_key`,
    `experimental_feature`, `feedback`, `fs`, `hook`, `item`, `mcp`,
    `model`, `notification`, `permissions`, `plugin`, `process`, `realtime`,
    `review`, `thread`, `thread_data`, `turn`, and `windows_sandbox`.
    - Moved v2 tests into `protocol/v2/tests.rs` so `mod.rs` stays small.
    - Kept shared protocol helpers in `protocol/v2/shared.rs`, including the
    enum mirroring macro and common cross-resource types.
    - Co-located resource-specific notifications and server-request payloads
    with the modules that own those resources.
    - Regenerated app-server protocol schema fixtures. The schema diffs are
    non-semantic newline-only changes after the refactor.
    
    ## Verification
    
    - `cargo check -p codex-app-server-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `just write-app-server-schema`
  • fix build (#21261)
    I believe a merge race in https://github.com/openai/codex/pull/20689
    broke the build, so this is a quick fix.
    
    `cargo check --tests` passed locally.
  • codex: use ThreadStore history for core review forks (#20577)
    - fork loaded parent threads from `ThreadStore` history in core agent
    control paths
    - migrate guardian review fork history to loaded session history instead
    of rereading rollout files
    
    ## Verification
    - `cargo test -p codex-core spawn_agent_fork`
  • Add cloud executor registration to exec-server (#19575)
    ## Summary
    This PR adds the first `codex-rs` milestone for remote-exec e2e: a local
    `codex exec-server` can now register itself with
    `codex-cloud-environments` and attach to the returned rendezvous
    websocket.
    
    At a high level, `codex exec-server --cloud ...` now:
    - loads ChatGPT auth from normal Codex config
    - registers an executor with `codex-cloud-environments`
    - receives a signed rendezvous websocket URL
    - serves the existing exec-server JSON-RPC protocol over that websocket
    
    ## What Changed
    - Added `--cloud`, `--cloud-base-url`, `--cloud-environment-id`, and
    `--cloud-name` to `codex exec-server`
    - Added a new `exec-server/src/cloud.rs` module that handles:
      - registration requests
      - auth/header setup
      - bounded auth retry on `401/403`
      - reconnect/backoff after websocket disconnects
    - Reused the existing `ConnectionProcessor` / `ExecServerHandler` path
    so cloud mode serves the same exec/filesystem RPC surface as local
    websocket mode
    - Added cloud-specific error variants and minimal docs for the new mode
    
    ## Testing
    Manual e2e test that fully goes through exec server flow with our codex
    cloud agent as orchestrator
  • Inject state DB, agent graph store (#20689)
    ## Why
    
    We want the agent graph store to be passed down the stack as a real
    dependency, the same way we already treat the thread store.
    
    This will let us inject the agent graph store as a real dependency and
    support implementations other than the local SQLite-backed one. Right
    now most code instantiates a state DB and an agent graph store
    just-in-time. Ideally, we would not depend on the state DB directly but
    only read through the higher-level interfaces.
    
    This change makes the dependency boundaries explicit and moves state DB
    initialization to process bootstrap instead of hiding it inside local
    store implementations.
    
    ## What changed
    
    - `ThreadManager` now requires a `StateDbHandle` and an
    `AgentGraphStore` at construction time instead of treating them as
    optional internals.
    - The local store constructors no longer lazily initialize SQLite.
    Callers now initialize the state DB once per process and use that shared
    handle to build:
      - `LocalThreadStore`
      - `LocalAgentGraphStore`
    - App bootstraps (`app-server`, `mcp-server`, `prompt_debug`, and the
    thread-manager sample) now initialize the state DB up front and inject
    the resulting handle down the stack.
    - `app-server` now consistently uses its process-scoped state DB handle
    instead of reopening SQLite or trying to recover it from loaded threads.
    - Device-key storage now reuses the shared state DB handle instead of
    maintaining its own lazy opener.
    - The thread archive / descendant traversal paths now use the injected
    `AgentGraphStore` instead of reaching through local
    thread-store-specific state.
    
    ## Verification
    
    - `cargo check -p codex-core -p codex-thread-store -p codex-app-server
    -p codex-mcp-server -p codex-thread-manager-sample --tests`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-core
    thread_manager_accepts_separate_agent_graph_store_and_thread_store --
    --nocapture`
    - `cargo test -p codex-app-server
    thread_archive_archives_spawned_descendants -- --nocapture`
  • Enable V8 sandboxing for source-built builds (#21146)
    ## Summary
    
    This is the first PR in the V8 in-process sandboxing rollout.
    
    It adds the build-system and Rust feature plumbing needed to support
    sandboxed V8 builds, then enables sandboxing by default for the
    source-built Bazel V8 path that we control directly. It deliberately
    keeps the published `rusty_v8` artifact workflows on their current
    non-sandboxed contract so this PR can land and ship independently before
    we change any released artifacts.
    
    ## Rollout plan
    
    - [x] **PR 1: land sandbox plumbing and default source-built Bazel V8 to
    sandboxed mode**
    
    - [ ] **PR 2: publish sandbox-enabled release artifacts and add
    compatibility validation**
    - Produce sandboxed artifact pairs for every released Cargo target that
    does not already use the source-built Bazel path.
    - Add CI coverage that consumes those sandboxed artifacts and verifies:
        - `codex-v8-poc` reports sandbox enabled
        - `codex-code-mode` builds/tests against the sandboxed path
    
    - [ ] **PR 3: switch release consumers to sandboxed artifacts by
    default**
      - Update released artifact selectors/checksums.
    - Enable the Rust `v8_enable_sandbox` feature in the default release
    path.
    - Make the sandboxed artifact family the normal path for published
    builds.
    
    - [ ] **PR 4: remove rollout-only compatibility paths**
    - Remove the temporary non-sandbox release compatibility config once the
    new default has shipped and baked.
      - Keep the invariant tests permanently.
  • [codex] fix TUI turn items view fixtures (#21243)
    ## Summary
    
    Adds the required `items_view` field to the three session picker `Turn`
    test fixtures that populate full turn item lists.
    
    ## Root Cause
    
    `#21063` added `Turn.items_view` to the app-server protocol type. The
    later session picker merge added three test-only
    `codex_app_server_protocol::Turn` literals without the new field, which
    broke Bazel compilation on `main` with `E0063: missing field
    items_view`.
    
    ## Validation
    
    - `just fmt`
    - `cargo test -p codex-tui resume_picker --no-fail-fast`
    - `just argument-comment-lint`
    
    I also ran `cargo test -p codex-tui`; it compiled and ran the suite, but
    this local machine failed two pre-existing status permission-profile
    tests because `/etc/codex/requirements.toml` disallows
    `DangerFullAccess`.
  • Auto-deny MCP elicitations for Xcode 26.4 clients (#21113)
    ## Summary
    
    Xcode 26.4 was built against app-server behavior from before MCP
    elicitation requests became client-visible in CLI 0.120.0 via #17043.
    That client line does not expect the new events/messages, so this PR
    restores the old behavior for exactly that client/version combination.
    
    The compatibility handling stays in the app-server layer: when the
    initialized client is `Xcode` and its version starts with `26.4`, the
    app server marks the live Codex thread so MCP elicitations are
    auto-denied. The flag is applied on thread start/resume/fork/turn
    attachment, carried through `Codex`/`CodexThread`, and stored on
    `McpConnectionManager` so refreshed MCP managers preserve the behavior.
    
    ## Notes
    
    This is intentionally narrow and includes a TODO to remove the
    compatibility path once Xcode 26.4 ages out.
  • [codex] Split tool handlers by tool name (#20687)
    ## Why
    
    Tool registration used to bind a tool name to a handler externally,
    which left ownership split between the registry plan and the handler
    implementation. Some built-in handlers also multiplexed multiple in-core
    tools by switching on the invoked tool name internally.
    
    This moves the registry identity onto the handler itself and makes
    built-in multi-tool areas use separate concrete handlers, so each
    registered handler instance owns exactly one tool name and one dispatch
    path.
    
    ## What Changed
    
    - Added `ToolHandler::tool_name()` and changed
    `ToolRegistryBuilder::register_handler` to derive the registry key from
    the handler.
    - Split built-in multiplexed handlers into concrete per-tool handlers
    for unified exec, shell/local shell/container exec, MCP resources, goal
    tools, and agent job tools.
    - Kept name-carrying handler instances only where the runtime target is
    inherently external or dynamic, such as MCP tools, dynamic tools, and
    unavailable placeholders.
    - Updated `ToolHandlerKind` and registry-plan construction so plan
    entries map directly to concrete handler registrations.
    
    ## Verification
    
    - `cargo test -p codex-tools tool_registry_plan`
    - `cargo test -p codex-core --lib tools::registry_tests`
    - `just fix -p codex-tools`
    - `just fix -p codex-core`
  • fix(linux-sandbox): isolate Linux sandbox synthetic mount registry per user for shared codex use case (#21234)
    ## Summary
    - make the Linux sandbox synthetic mount registry path unique per
    effective UID
    - keep same-user coordination intact while avoiding collisions between
    users sharing `/tmp`
    - add a regression test for the registry path contract
    
    ## Why
    Issue #21192 reports that the Linux sandbox currently uses one global
    temp path at `/tmp/codex-bwrap-synthetic-mount-targets`. If another user
    creates that directory first, later users can fail to open the shared
    lock file with `Permission denied`.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-linux-sandbox`
    - `cargo clippy -p codex-linux-sandbox --all-targets`
    
    Fixes #21192
  • fix(linux-sandbox): avoid panic on bwrap build failures (#21127)
    ## Summary
    
    - Propagate Linux bubblewrap argument-construction failures instead of
    panicking in the helper
    - Keep mutable-symlink carveouts fail-closed while reporting them as
    ordinary sandbox build failures
    - Add regression coverage for a protected `.codex` symlink inside a
    writable workspace root
    
    ## Root cause
    
    Linux bubblewrap intentionally rejects read-only carveouts that cross a
    symlink the sandboxed process can still rewrite. That is the correct
    security behavior for protected metadata paths such as `.codex`.
    
    The bug was one layer higher: `linux_run_main` treated the expected
    build failure as impossible and panicked while constructing the
    bubblewrap argv. For issue #20716, that turned a normal fail-closed
    sandbox outcome into a noisy panic in the transcript.
    
    ## User impact
    
    Users with a project-local `.codex` symlink inside a writable workspace
    still get the conservative sandbox decision, but they no longer see a
    Rust panic for that condition. The helper now exits with the concise
    sandbox-build error so the normal denial / escalation path can handle
    it.
    
    
    Fixes #20716
  • feat(tui): redesign session picker (#20065)
    ## Why
    
    The resume/fork picker is becoming the main way users recover previous
    work, but the old fixed table made sessions hard to scan once thread
    names, branches, working directories, and timestamps all mattered. This
    redesign makes the picker denser by default, easier to search, and safer
    to inspect before resuming or forking.
    
    <table>
    <tr>
    <td>
    <img width="1660" height="1103" alt="CleanShot 2026-05-03 at 12 34 10"
    src="https://github.com/user-attachments/assets/313ede1d-1da4-4863-acd2-56b3e27e9703"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 34 15"
    src="https://github.com/user-attachments/assets/cfde7d5c-bab0-4994-a807-254e53f344ea"
    />
    </td>
    </tr>
    <tr>
    <td>
    <img width="1664" height="1107" alt="CleanShot 2026-05-03 at 12 39 22"
    src="https://github.com/user-attachments/assets/e1ee58ca-4dc5-4a35-ae0f-47562da3974c"
    />
    </td>
    <td>
    <img width="1662" height="1100" alt="CleanShot 2026-05-03 at 12 35 09"
    src="https://github.com/user-attachments/assets/9c888072-eedf-4f45-985c-0c14df28bcc7"
    />
    </td>
    </tr>
    </table>
    
    ## What Changed
    
    - Replaces the old session table with responsive session rows that
    prioritize the session name or preview, then show timestamp, cwd, and
    branch metadata.
    - Makes dense view the default while keeping comfortable view available
    through `Ctrl+O`.
    - Persists the picker view preference in `[tui].session_picker_view`,
    including active profile-scoped config.
    - Adds sort/filter controls for updated time, created time, cwd, and all
    sessions.
    - Expands search matching across session name, preview, thread id,
    branch, and cwd.
    - Makes `Esc` safer in search mode: it clears an active query before
    starting a new session.
    - Adds lazy transcript inspection:
      - `Space` expands recent transcript context inline.
      - `Ctrl+T` opens a transcript overlay.
      - raw reasoning visibility follows `show_raw_agent_reasoning`.
    - Keeps remote cwd filtering server-side for remote app-server sessions
    so local path normalization does not incorrectly hide remote results.
    - Updates snapshots and config schema for the new picker states and
    config option.
    
    ## How to Test
    
    1. Start Codex in a repo with several saved sessions.
    2. Press `Ctrl+R` / resume picker entry point.
    3. Confirm the picker opens in dense mode and shows session name or
    preview, timestamp, cwd, and branch metadata.
    4. Press `Ctrl+O` and confirm it switches between dense and comfortable
    views.
    5. Restart Codex and confirm the selected view persists.
    6. Type a query that matches a branch, cwd, thread id, or session name;
    confirm matching sessions appear.
    7. Press `Esc` while the query is non-empty and confirm it clears search
    instead of starting a new session.
    8. Select a session and press `Space`; confirm recent transcript context
    expands inline.
    9. Press `Ctrl+T`; confirm the transcript overlay opens and respects
    raw-reasoning visibility settings.
    
    Targeted tests:
    - `cargo test -p codex-tui resume_picker --no-fail-fast`
    - `cargo test -p codex-core
    runtime_config_resolves_session_picker_view_default_and_override`
    - `cargo test -p codex-core profile_tui_rejects_unsupported_settings`
    - `cargo check -p codex-thread-manager-sample`
    - `cargo insta pending-snapshots`
  • feat(tui): route /diff through workspace commands (#21001)
    Stacked on #20892.
    
    ## Why
    
    #20892 adds the TUI workspace command abstraction so branch status
    metadata can run through app-server instead of assuming the CLI process
    has the active workspace locally. `/diff` still used direct local
    process execution, which means remote app-server sessions could compute
    the diff against the wrong machine or fail to see the active workspace
    at all.
    
    This PR moves `/diff` onto that same app-server-backed command path so
    Git runs wherever the active workspace lives.
    
    ## What Changed
    
    - Route `/diff` through the TUI `WorkspaceCommandExecutor` using the
    active chat cwd.
    - Replace direct `tokio::process::Command` usage in `get_git_diff` with
    argv-based workspace command requests.
    - Preserve the existing `/diff` behavior: tracked diff output, untracked
    file diffs, treating Git diff exit code `1` as success, and showing the
    existing non-git-repository message.
    - Extend `WorkspaceCommand` with caller-set timeouts and an explicit
    uncapped-output opt-out. Metadata probes remain capped by default;
    `/diff` opts out because its full output is the user-visible payload.
    
    ## How to Test
    
    Manual reviewer path:
    
    1. Start the Codex TUI from a Git worktree with one tracked file change
    and one untracked file.
    2. Run `/diff`.
    3. Confirm the rendered diff includes both the tracked diff and the
    untracked file diff.
    4. Start the TUI outside a Git worktree, or switch to a non-git cwd,
    then run `/diff`.
    5. Confirm it shows the existing `/diff` not-inside-a-git-repository
    message.
    
    Targeted tests run:
    
    - `cargo test -p codex-tui get_git_diff -- --nocapture`
    - `cargo test -p codex-tui branch_summary -- --nocapture`
    - `cargo test -p codex-tui`
  • add turn items view to app-server turns (#21063)
    ## Why
    
    `Turn.items` currently overloads an empty array to mean either that no
    items exist or that the server intentionally did not load them for this
    response. That ambiguity blocks future lazy-loading work where clients
    need to distinguish unloaded, summary, and fully hydrated turn payloads.
    
    ## What changed
    
    - add a new `TurnItemsView` enum with `notLoaded`, `summary`, and `full`
    variants
    - add required `itemsView` metadata to app-server `Turn` payloads
    - mark reconstructed persisted history as `full` and live shell-style
    turn payloads as `notLoaded`
    - keep current `thread/turns/list` behavior unchanged and document that
    it still returns `full` turns today
    - regenerate the JSON and TypeScript protocol fixtures
    
    ## Verification
    
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server thread_read_can_include_turns`
    - `cargo test -p codex-app-server
    thread_turns_list_can_page_backward_and_forward`
    - `cargo test -p codex-app-server
    thread_resume_rejects_history_when_thread_is_running`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-app-server`
    - `just fmt`
  • [codex] Use shared app-server JSON-RPC error helpers (#21221)
    ## Why
    
    App-server had repeated hand-built JSON-RPC error objects for standard
    error shapes. Using the shared helpers keeps the common
    `invalid_request`, `invalid_params`, and `internal_error` construction
    in one place and reduces the chance of new call sites drifting from the
    common error payload shape.
    
    ## What changed
    
    - Replaced manual standard JSON-RPC error object creation with
    `internal_error(...)`, `invalid_request(...)`, and `invalid_params(...)`
    across app-server request processors and runtime paths.
    - Removed local duplicate helper definitions from search and review
    request handling.
    - Preserved existing structured `data` payloads by creating the shared
    helper error first and then attaching the existing metadata.
    - Left custom non-standard errors and raw error-code assertions intact.
    
    ## Validation
    
    - `cargo test -p codex-app-server`
  • hook trust metadata and enforcement (#20321)
    # Why
    
    We want shared hook trust that both the app and the TUI can build on,
    but the metadata is only useful if runtime behavior agrees with it. This
    PR adds a single backend trust model for hooks so unmanaged hooks cannot
    run until the current definition has been reviewed, while managed hooks
    remain runnable and non-configurable.
    
    # What
    
    - persist `trusted_hash` alongside hook state in `config.toml`
    - expose `currentHash` and derived `trustStatus` through `hooks/list`
    - derive trust from normalized hook definitions so equivalent hooks from
    `config.toml` and `hooks.json` share the same trust identity
    - gate unmanaged hooks on trust before they enter the runnable handler
    set
    
    # Reviewer Notes
    
    - key file to review is `codex-rs/hooks/src/engine/discovery.rs`
    - the only **core** change is schema related
  • Route process tools to selected environments (#20647)
    ## Why
    When a turn exposes multiple selected environments, shell-style tools
    need a model-facing way to identify the intended target environment and
    handlers need to resolve that target before parsing cwd-relative
    permission fields or launching processes.
    
    This PR scopes that rollout to process tools. Filesystem-oriented tools
    such as `apply_patch`, `view_image`, and `list_dir` are intentionally
    left for follow-up slices.
    
    ## What Changed
    - Adds an `include_environment_id` option to shell-style tool schema
    builders.
    - Exposes optional `environment_id` on `shell`, `shell_command`, and
    `exec_command` only when `ToolEnvironmentMode::Multiple` is active.
    - Adds a shared handler helper that parses `environment_id` and
    `workdir` from JSON function-call arguments and returns the selected
    `Environment` plus effective absolute cwd.
    - Uses that helper in `shell`, `shell_command`, and `exec_command`
    handling so process execution uses the selected environment filesystem
    and cwd.
    - Changes `ExecCommandRequest` to carry a required resolved `cwd`,
    removing the process-manager fallback to the primary turn cwd for new
    exec commands.
    - Leaves `write_stdin` unchanged because it targets an existing process
    id, not a new environment.
    
    ## Testing
    - Added unit coverage for process-tool schema exposure, selected
    environment resolution, primary fallback, no-environment handling,
    unknown environment ids, and resolving cwd-relative permission paths
    against the selected environment cwd.
    - Added a remote-suite e2e coverage case for `exec_command` routing
    across explicit zero environments, one local environment, and
    local+remote environments.
    - Ran `just fmt` and `git diff --check`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex-analytics] add tool item event schemas (#17089)
    ## Why
    
    Tool analytics need stable, typed payloads before the later lifecycle
    reducer starts emitting them. Keeping the event schema definitions
    isolated in their own PR makes the emitted surface reviewable separately
    from the reducer logic that produces those events.
    
    ## What changed
    
    - Adds the common tool-item analytics event base plus event payload
    types for command execution, file changes, MCP calls, dynamic tools,
    collaboration tools, web search, and image generation.
    - Extends `TrackEventRequest` with the corresponding tool-item variants.
    - Adds serialization coverage for the command-execution event shape.
    
    ## Verification
    
    - `cargo test -p codex-analytics`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/17089).
    * #18748
    * #18747
    * #17090
    * __->__ #17089
    * #20514
  • app-server: ignore persist_extended_history param (#21225)
    ## Why
    
    Taking a step to removing the `persistExtendedHistory` field. It's not
    scalable to be persisting so much data in the rollout file and returning
    it in the thread history.
    
    When a client explicitly sends `true`, the server now tells that client
    the parameter is deprecated and ignored so the caller has a clear
    migration signal via the `deprecationNotice` notification.
    
    ## What changed
    
    - Keep the `persist_extended_history` / `persistExtendedHistory` field
    in the v2 protocol for compatibility, but document it as deprecated and
    ignored.
    - Ignore the parameter in app-server `thread/start`, `thread/resume`,
    and `thread/fork`; those paths always use limited history persistence
    now.
    - Stop treating `persistExtendedHistory` as a running-thread resume
    override mismatch.
    - Emit a connection-scoped `deprecationNotice` when a request explicitly
    sets `persist_extended_history: true`.
    
    ## Verification
    
    - Added `thread_start_deprecates_persist_extended_history_true` to cover
    the deprecation notice.
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-app-server-protocol`
  • feat(tui): add raw scrollback mode (#20819)
    ## Why
    
    Granular copy is particularly difficult with the current output. Part of
    it was solved with the introduction of the `/copy` command but when you
    only need to copy parts of a response, you still encounter some issues:
    
    - When you copy a paragraph, the result is a sequence of separate lines
    instead of one correctly joined paragraph.
    - When a word wraps, part of it stays on the original line and the rest
    appears at the start of the next line.
    - When you copy a long command, extra line breaks are often inserted,
    and command arguments can be split across multiple lines.
    
    
    https://github.com/user-attachments/assets/0ef85c84-9363-4aad-b43a-15fce062a443
    
    ## Solution
    
    Now that we own the scrollback and we re-create it when we resize, we
    have the opportunity of toggling between the raw text and the rich text
    we see today.
    
    - Add TUI raw scrollback mode with `tui.raw_output_mode`, `/raw
    [on|off]`, and the configurable `tui.keymap.global.toggle_raw_output`
    action.
    - Render transcript cells through rich/raw-aware paths so raw mode
    preserves source text and lets the terminal soft-wrap selection-friendly
    output.
    - Bind raw-mode toggle to `alt-r` by default, with the keybinding path
    toggling silently while `/raw` continues to emit confirmation messages.
    
    ## Related Issues
    
    Likely addressed by raw mode:
    
    - #12200: clean copy for multiline and soft-wrapped output. Raw mode
    removes Codex-inserted wrapping/indentation and lets the terminal
    soft-wrap logical lines.
    - #9252: command suggestions gain unwanted leading spaces when copied.
    Raw mode renders transcript text without the rich-mode left
    padding/gutter.
    - #8258: prompt output is hard to copy because of leading indentation.
    Raw mode renders user/source-backed transcript text without that
    decorative indentation.
    
    Partially or conditionally addressed:
    
    - #2880: copy/export message as Markdown. Raw mode exposes raw Markdown
    for terminal selection, but this PR does not add a dedicated
    export/copy-message command.
    - #19820: mouse drag selection + copy in the TUI. Raw mode improves
    terminal-native selection of output/history text, but this PR does not
    implement in-TUI mouse selection, highlighting, auto-copy, or composer
    selection.
    - #18979: copied content is divided into two parts. This should improve
    cases caused by Codex-inserted wraps/padding in rendered output; if the
    report is about pasting into the composer/input path, that remains
    outside this PR.
    
    ## Validation
    
    - `just write-config-schema`
    - `just fmt`
    - `cargo test -p codex-config`
    - `cargo test -p codex-tui`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    - `cargo test -p codex-tui
    raw_output_mode_can_change_without_inserting_notice -- --nocapture`
    - `cargo test -p codex-tui
    raw_slash_command_toggles_and_accepts_on_off_args -- --nocapture`
    - `cargo test -p codex-tui raw_output_toggle -- --nocapture`
    - `git diff --check`
    - `cargo insta pending-snapshots`
  • chore: add minimal proxy egress diagnostics (#21220)
    ## Why
    Recent Auto Review reports show Git traffic hanging through the local
    proxy on both SSH and HTTPS paths. Today the support bundle does not
    make it obvious whether a request is stuck before upstream dialing,
    during the proxy hop, or after the upstream response begins, which slows
    down root-cause triage.
    
    This adds a small amount of runtime visibility at the existing proxy
    boundaries without changing routing or policy behavior.
    
    ## What changed
    - log whether HTTP and CONNECT traffic take the direct or upstream-proxy
    route
    - log start / success / failure timings for CONNECT, HTTP, and SOCKS5
    upstream dials
    - log CONNECT forwarding lifecycle events
    - describe HTTP success at the response-header boundary that is actually
    observed, rather than implying the full body finished
    
    ## Verification
    - `cargo test -p codex-network-proxy`
    - `cargo clippy -p codex-network-proxy --all-targets -- -D warnings`