Commit Graph

397 Commits

  • app-server: improve thread resume rejoin flow (#11776)
    thread/resume response includes latest turn with all items, in band so
    no events are stale or lost
    
    Testing
    - e2e tested using app-server-test-client using flow described in
    "Testing Thread Rejoin Behavior" in
    codex-rs/app-server-test-client/README.md
    - e2e tested in codex desktop by reconnecting to a running turn
  • app-server: fix flaky list_apps_returns_connectors_with_accessible_flags test (#12286)
    ## Why
    
    `app/list` emits `app/list/updated` after whichever async load finishes
    first (directory connectors or accessible tools). This test assumed the
    directory-backed update always arrived first because it injected a tools
    delay, but that assumption is not stable when the process-global Codex
    Apps tools cache is already warm. In that case the accessible-tools path
    can return immediately and the first notification shape flips, which
    makes the assertion flaky.
    
    Relevant code paths:
    
    -
    [`codex-rs/app-server/src/codex_message_processor.rs`](https://github.com/openai/codex/blob/13ec97d72e3482f16c62e0a22025a0542133e623/codex-rs/app-server/src/codex_message_processor.rs#L4949-L5034)
    (concurrent loads + per-load `app/list/updated` notifications)
    -
    [`codex-rs/core/src/mcp_connection_manager.rs`](https://github.com/openai/codex/blob/13ec97d72e3482f16c62e0a22025a0542133e623/codex-rs/core/src/mcp_connection_manager.rs#L1182-L1197)
    (Codex Apps tools cache hit path)
    
    ## What Changed
    
    Updated
    `suite::v2::app_list::list_apps_returns_connectors_with_accessible_flags`
    in `codex-rs/app-server/tests/suite/v2/app_list.rs` to accept either
    valid first `app/list/updated` payload:
    
    - the directory-first snapshot
    - the accessible-tools-first snapshot
    
    The test still keeps the later assertions strict:
    
    - the second `app/list/updated` notification must be the fully merged
    result
    - the final `app/list` response must match the same merged result
    
    I also added an inline comment explaining why the first notification is
    intentionally order-insensitive.
    
    ## Verification
    
    - `cargo test -p codex-app-server`
  • tests: centralize in-flight turn cleanup helper (#12271)
    ## Why
    
    Several tests intentionally exercise behavior while a turn is still
    active. The cleanup sequence for those tests (`turn/interrupt` + waiting
    for `codex/event/turn_aborted`) was duplicated across files, which made
    the rationale easy to lose and the pattern easy to apply inconsistently.
    
    This change centralizes that cleanup in one place with a single
    explanatory doc comment.
    
    ## What Changed
    
    ### Added shared helper
    
    In `codex-rs/app-server/tests/common/mcp_process.rs`:
    
    - Added `McpProcess::interrupt_turn_and_wait_for_aborted(...)`.
    - Added a doc comment explaining why explicit interrupt + terminal wait
    is required for tests that intentionally leave a turn in-flight.
    
    ### Migrated call sites
    
    Replaced duplicated interrupt/aborted blocks with the helper in:
    
    - `codex-rs/app-server/tests/suite/v2/thread_resume.rs`
      - `thread_resume_rejects_history_when_thread_is_running`
      - `thread_resume_rejects_mismatched_path_when_thread_is_running`
    - `codex-rs/app-server/tests/suite/v2/turn_start_zsh_fork.rs`
      - `turn_start_shell_zsh_fork_executes_command_v2`
    -
    `turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2`
    - `codex-rs/app-server/tests/suite/v2/turn_steer.rs`
      - `turn_steer_returns_active_turn_id`
    
    ### Existing cleanup retained
    
    In `codex-rs/app-server/tests/suite/v2/turn_start.rs`:
    
    - `turn_start_accepts_local_image_input` continues to explicitly wait
    for `turn/completed` so the turn lifecycle is fully drained before test
    exit.
    
    ## Verification
    
    - `cargo test -p codex-app-server`
  • tests(thread_resume): interrupt running turns in resume error-path tests (#12269)
    ## Why
    
    `thread_resume` tests can intentionally create an in-flight turn, assert
    a `thread/resume` error path, and return immediately. That leaves turn
    work active during teardown, which can surface as intermittent `LEAK`
    failures.
    
    Sample output that motivated this investigation (reported during test
    runs):
    
    ```text
    LEAK ... codex-app-server::all suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch
    ```
    
    ## What Changed
    
    Updated only `codex-rs/app-server/tests/suite/v2/thread_resume.rs`:
    
    - `thread_resume_rejects_history_when_thread_is_running`
    - `thread_resume_rejects_mismatched_path_when_thread_is_running`
    
    Both tests now:
    
    1. capture the running turn id from `TurnStartResponse`
    2. assert the expected `thread/resume` error
    3. call `turn/interrupt` for that running turn
    4. wait for `codex/event/turn_aborted` before returning
    
    ## Why This Is The Correct Fix
    
    These tests are specifically validating resume behavior while a turn is
    active. They should also own cleanup of that active turn before exiting.
    Explicitly interrupting and waiting for the terminal abort notification
    removes teardown races and avoids relying on process-drop behavior to
    clean up in-flight work.
    
    ## Repro / Verification
    
    Repro command used for investigation:
    
    ```bash
    cargo nextest run -p codex-app-server -j 2 --no-fail-fast --stress-count 50 --status-level leak --final-status-level fail -E 'test(suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch) | test(suite::v2::thread_resume::thread_resume_rejects_history_when_thread_is_running) | test(suite::v2::thread_resume::thread_resume_rejects_mismatched_path_when_thread_is_running) | test(suite::v2::thread_resume::thread_resume_keeps_in_flight_turn_streaming)'
    ```
    
    Observed before this change: intermittent `LEAK` in
    `thread_resume_rejects_history_when_thread_is_running`.
    
    Also verified with:
    
    - `cargo test -p codex-app-server`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/12269).
    * #12271
    * __->__ #12269
  • Add configurable MCP OAuth callback URL for MCP login (#11382)
    ## Summary
    
    Implements a configurable MCP OAuth callback URL override for `codex mcp
    login` and app-server OAuth login flows, including support for non-local
    callback endpoints (for example, devbox ingress URLs).
    
    ## What changed
    
    - Added new config key: `mcp_oauth_callback_url` in
    `~/.codex/config.toml`.
    - OAuth authorization now uses `mcp_oauth_callback_url` as
    `redirect_uri` when set.
    - Callback handling validates the callback path against the configured
    redirect URI path.
    - Listener bind behavior is now host-aware:
    - local callback URL hosts (`localhost`, `127.0.0.1`, `::1`) bind to
    `127.0.0.1`
      - non-local callback URL hosts bind to `0.0.0.0`
    - `mcp_oauth_callback_port` remains supported and is used for the
    listener port.
    - Wired through:
      - CLI MCP login flow
      - App-server MCP OAuth login flow
      - Skill dependency OAuth login flow
    - Updated config schema and config tests.
    
    ## Why
    
    Some environments need OAuth callbacks to land on a specific reachable
    URL (for example ingress in remote devboxes), not loopback. This change
    allows that while preserving local defaults for existing users.
    
    ## Backward compatibility
    
    - No behavior change when `mcp_oauth_callback_url` is unset.
    - Existing `mcp_oauth_callback_port` behavior remains intact.
    - Local callback flows continue binding to loopback by default.
    
    ## Testing
    
    - `cargo test -p codex-rmcp-client callback -- --nocapture`
    - `cargo test -p codex-core --lib mcp_oauth_callback -- --nocapture`
    - `cargo check -p codex-cli -p codex-app-server -p codex-rmcp-client`
    
    ## Example config
    
    ```toml
    mcp_oauth_callback_port = 5555
    mcp_oauth_callback_url = "https://<devbox>-<namespace>.gateway.<cluster>.internal.api.openai.org/callback"
  • app-server tests: reduce intermittent nextest LEAK via graceful child shutdown (#12266)
    ## Why
    `cargo nextest` was intermittently reporting `LEAK` for
    `codex-app-server` tests even when assertions passed. This adds noise
    and flakiness to local/CI signals.
    
    Sample output used as the basis of this investigation:
    
    ```text
    LEAK [   7.578s] ( 149/3663) codex-app-server::all suite::output_schema::send_user_turn_output_schema_is_per_turn_v1
    LEAK [   7.383s] ( 210/3663) codex-app-server::all suite::v2::dynamic_tools::dynamic_tool_call_round_trip_sends_text_content_items_to_model
    LEAK [   7.768s] ( 213/3663) codex-app-server::all suite::v2::dynamic_tools::thread_start_injects_dynamic_tools_into_model_requests
    LEAK [   8.841s] ( 224/3663) codex-app-server::all suite::v2::output_schema::turn_start_accepts_output_schema_v2
    LEAK [   8.151s] ( 225/3663) codex-app-server::all suite::v2::plan_item::plan_mode_uses_proposed_plan_block_for_plan_item
    LEAK [   8.230s] ( 232/3663) codex-app-server::all suite::v2::safety_check_downgrade::openai_model_header_mismatch_emits_model_rerouted_notification_v2
    LEAK [   6.472s] ( 273/3663) codex-app-server::all suite::v2::turn_start::turn_start_accepts_collaboration_mode_override_v2
    LEAK [   6.107s] ( 275/3663) codex-app-server::all suite::v2::turn_start::turn_start_accepts_personality_override_v2
    ```
    
    ## How I Reproduced
    I focused on the suspect tests and ran them under `nextest` stress mode
    with leak reporting enabled.
    
    ```bash
    cargo nextest run -p codex-app-server -j 2 --no-fail-fast --stress-count 25 --status-level leak --final-status-level fail -E 'test(suite::output_schema::send_user_turn_output_schema_is_per_turn_v1) | test(suite::v2::dynamic_tools::dynamic_tool_call_round_trip_sends_text_content_items_to_model) | test(suite::v2::dynamic_tools::thread_start_injects_dynamic_tools_into_model_requests) | test(suite::v2::output_schema::turn_start_accepts_output_schema_v2) | test(suite::v2::plan_item::plan_mode_uses_proposed_plan_block_for_plan_item) | test(suite::v2::safety_check_downgrade::openai_model_header_mismatch_emits_model_rerouted_notification_v2) | test(suite::v2::turn_start::turn_start_accepts_collaboration_mode_override_v2) | test(suite::v2::turn_start::turn_start_accepts_personality_override_v2)'
    ```
    
    This reproduced intermittent `LEAK` statuses while tests still passed.
    
    ## What Changed
    In `codex-rs/app-server/tests/common/mcp_process.rs`:
    
    - Changed `stdin: ChildStdin` to `stdin: Option<ChildStdin>` so teardown
    can explicitly close stdin.
    - In `Drop`, close stdin first to trigger EOF-based graceful shutdown.
    - Wait briefly for graceful exit.
    - If still running, fall back to `start_kill()` and the existing bounded
    `try_wait()` loop.
    - Updated send-path handling to bail if stdin is already closed.
    
    ## Why This Is the Right Fix
    The leak signal was caused by child-process teardown timing, not
    test-logic assertion failure. The helper previously relied mostly on
    force-kill timing in `Drop`; that can race with nextest leak detection.
    
    Closing stdin first gives `codex-app-server` a deterministic, graceful
    shutdown path before force-kill. Keeping the force-kill fallback
    preserves robustness if graceful shutdown does not complete in time.
    
    ## Verification
    - `cargo test -p codex-app-server`
    - Re-ran the stress repro above after this change: no `LEAK` statuses
    observed.
    - Additional high-signal stress run also showed no leaks:
    
    ```bash
    cargo nextest run -p codex-app-server -j 2 --no-fail-fast --stress-count 100 --status-level leak --final-status-level fail -E 'test(suite::output_schema::send_user_turn_output_schema_is_per_turn_v1) | test(suite::v2::dynamic_tools::dynamic_tool_call_round_trip_sends_text_content_items_to_model)'
    ```
  • client side modelinfo overrides (#12101)
    TL;DR
    Add top-level `model_catalog_json` config support so users can supply a
    local model catalog override from a JSON file path (including adding new
    models) without backend changes.
    
    ### Problem
    Codex previously had no clean client-side way to replace/overlay model
    catalog data for local testing of model metadata and new model entries.
    
    ### Fix
    - Add top-level `model_catalog_json` config field (JSON file path).
    - Apply catalog entries when resolving `ModelInfo`:
      1. Base resolved model metadata (remote/fallback)
      2. Catalog overlay from `model_catalog_json`
    3. Existing global top-level overrides (`model_context_window`,
    `model_supports_reasoning_summaries`, etc.)
    
    ### Note
    Will revisit per-field overrides in a follow-up
    
    ### Tests
    Added tests
  • Stabilize app-server detached review and running-resume tests (#12203)
    ## Summary
    - stabilize
    `thread_resume_rejoins_running_thread_even_with_override_mismatch` by
    using a valid delayed second SSE response instead of an intentionally
    truncated stream
    - set `RUST_MIN_STACK=4194304` for spawned app-server test processes in
    `McpProcess` to avoid stack-sensitive CI overflows in detached review
    tests
    
    ## Why
    - the thread-resume assertion could race with a mocked stream-disconnect
    error and intermittently observe `systemError`
    - detached review startup is stack-sensitive in some CI environments;
    pinning a larger stack in the test harness removes that flake without
    changing product behavior
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server --test all
    suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch`
    - `cargo test -p codex-app-server --test all
    suite::v2::review::review_start_with_detached_delivery_returns_new_thread_id`
  • app-server: expose loaded thread status via read/list and notifications (#11786)
    Motivation
    - Today, a newly connected client has no direct way to determine the
    current runtime status of threads from read/list responses alone.
    - This forces clients to infer state from transient events, which can
    lead to stale or inconsistent UI when reconnecting or attaching late.
    
    Changes
    - Add `status` to `thread/read` responses.
    - Add `statuses` to `thread/list` responses.
    - Emit `thread/status/changed` notifications with `threadId` and the new
    status.
    - Track runtime status for all loaded threads and default unknown
    threads to `idle`.
    - Update protocol/docs/tests/schema fixtures for the revised API.
    
    Testing
    - Validated protocol API changes with automated protocol tests and
    regenerated schema/type fixtures.
    - Validated app-server behavior with unit and integration test suites,
    including status transitions and notifications.
  • app-server support for Windows sandbox setup. (#12025)
    app-server support for initiating Windows sandbox setup.
    server responds quickly to setup request and makes a future RPC call
    back to client when the setup finishes.
    
    The TUI implementation is unaffected but in a future PR I'll update the
    TUI to use the shared setup helper
    (`windows_sandbox.run_windows_sandbox_setup`)
  • feat(core): zsh exec bridge (#12052)
    zsh fork PR stack:
    - https://github.com/openai/codex/pull/12051 
    - https://github.com/openai/codex/pull/12052 👈 
    
    ### Summary
    This PR introduces a feature-gated native shell runtime path that routes
    shell execution through a patched zsh exec bridge, removing MCP-specific
    behavior from the shell hot path while preserving existing
    CommandExecution lifecycle semantics.
    
    When shell_zsh_fork is enabled, shell commands run via patched zsh with
    per-`execve` interception through EXEC_WRAPPER. Core receives wrapper
    IPC requests over a Unix socket, applies existing approval policy, and
    returns allow/deny before the subcommand executes.
    
    ### What’s included
    **1) New zsh exec bridge runtime in core**
    - Wrapper-mode entrypoint (maybe_run_zsh_exec_wrapper_mode) for
    EXEC_WRAPPER invocations.
    - Per-execution Unix-socket IPC handling for wrapper requests/responses.
    - Approval callback integration using existing core approval
    orchestration.
    - Streaming stdout/stderr deltas to existing command output event
    pipeline.
    - Error handling for malformed IPC, denial/abort, and execution
    failures.
    
    **2) Session lifecycle integration**
    SessionServices now owns a `ZshExecBridge`.
    Session startup initializes bridge state; shutdown tears it down
    cleanly.
    
    **3) Shell runtime routing (feature-gated)**
    When `shell_zsh_fork` is enabled:
    - Build execution env/spec as usual.
    - Add wrapper socket env wiring.
    - Execute via `zsh_exec_bridge.execute_shell_request(...)` instead of
    the regular shell path.
    - Non-zsh-fork behavior remains unchanged.
    
    **4) Config + feature wiring**
    - Added `Feature::ShellZshFork` (under development).
    - Added config support for `zsh_path` (optional absolute path to patched
    zsh):
    - `Config`, `ConfigToml`, `ConfigProfile`, overrides, and schema.
    - Session startup validates that `zsh_path` exists/usable when zsh-fork
    is enabled.
    - Added startup test for missing `zsh_path` failure mode.
    
    **5) Seatbelt/sandbox updates for wrapper IPC**
    - Extended seatbelt policy generation to optionally allow outbound
    connection to explicitly permitted Unix sockets.
    - Wired sandboxing path to pass wrapper socket path through to seatbelt
    policy generation.
    - Added/updated seatbelt tests for explicit socket allow rule and
    argument emission.
    
    **6) Runtime entrypoint hooks**
    - This allows the same binary to act as the zsh wrapper subprocess when
    invoked via `EXEC_WRAPPER`.
    
    **7) Tool selection behavior**
    - ToolsConfig now prefers ShellCommand type when shell_zsh_fork is
    enabled.
    - Added test coverage for precedence with unified-exec enabled.
  • feat(core): plumb distinct approval ids for command approvals (#12051)
    zsh fork PR stack:
    - https://github.com/openai/codex/pull/12051 👈 
    - https://github.com/openai/codex/pull/12052
    
    With upcoming support for a fork of zsh that allows us to intercept
    `execve` and run execpolicy checks for each subcommand as part of a
    `CommandExecution`, it will be possible for there to be multiple
    approval requests for a shell command like `/path/to/zsh -lc 'git status
    && rg \"TODO\" src && make test'`.
    
    To support that, this PR introduces a new `approval_id` field across
    core, protocol, and app-server so that we can associate approvals
    properly for subcommands.
  • Chore: remove response model check and rely on header model for downgrade (#12061)
    ### Summary
    Ensure that we use the model value from the response header only so that
    we are guaranteed with the correct slug name. We are no longer checking
    against the model value from response so that we are less likely to have
    false positive.
    
    There are two different treatments - for SSE we use the header from the
    response and for websocket we check top-level events.
  • app-server: Emit thread archive/unarchive notifications (#12030)
    * Add v2 server notifications `thread/archived` and `thread/unarchived`
    with a `threadId` payload.
    * Wire new events into `thread/archive` and `thread/unarchive` success
    paths.
    * Update app-server protocol/schema/docs accordingly.
    
    Testing:
    - Updated archive/unarchive end-to-end tests to verify both
    notifications are emitted with the expected thread id payload.
  • [apps] Expose more fields from apps listing endpoints. (#11706)
    - [x] Expose app_metadata, branding, and labels in AppInfo.
  • chore: rm remote models fflag (#11699)
    rm `remote_models` feature flag.
    
    We see issues like #11527 when a user has `remote_models` disabled, as
    we always use the default fallback `ModelInfo`. This causes issues with
    model performance.
    
    Builds on #11690, which helps by warning the user when they are using
    the default fallback. This PR will make that happen much less frequently
    as an accidental consequence of disabling `remote_models`.
  • Add remote skill scope/product_surface/enabled params and cleanup (#11801)
    skills/remote/list: params=hazelnutScope, productSurface, enabled;
    returns=data: { id, name, description }[]
    skills/remote/export: params=hazelnutId; returns={ id, path }
  • Feat: add model reroute notification (#12001)
    ### Summary
    Builiding off
    https://github.com/openai/codex/pull/11964/files/5c75aa7b89a70bc2cc410a6fd238749306ec4c5e#diff-058ae8f109a8b84b4b79bbfa45f522c2233b9d9e139696044ae374d50b6196e0,
    we have created a `model/rerouted` notification that captures the event
    so that consumers can render as expected. Keep the `EventMsg::Warning`
    path in core so that this does not affect TUI rendering.
    
    `model/rerouted` is meant to be generic to account for future usage
    including capacity planning etc.
  • add(core): safety check downgrade warning (#11964)
    Add per-turn notice when a request is downgraded to a fallback model due
    to cyber safety checks.
    
    **Changes**
    
    - codex-api: Emit a ServerModel event based on the openai-model response
    header and/or response payload (SSE + WebSocket), including when the
    model changes mid-stream.
    - core: When the server-reported model differs from the requested model,
    emit a single per-turn warning explaining the reroute to gpt-5.2 and
    directing users to Trusted
        Access verification and the cyber safety explainer.
    - app-server (v2): Surface these cyber model-routing warnings as
    synthetic userMessage items with text prefixed by Warning: (and document
    this behavior).
  • chore(core) rm Feature::RequestRule (#11866)
    ## Summary
    This feature is now reasonably stable, let's remove it so we can
    simplify our upcoming iterations here.
    
    ## Testing 
    - [x] Existing tests pass
  • fix: show user warning when using default fallback metadata (#11690)
    ### What
    It's currently unclear when the harness falls back to the default,
    generic `ModelInfo`. This happens when the `remote_models` feature is
    disabled or the model is truly unknown, and can lead to bad performance
    and issues in the harness.
    
    Add a user-facing warning when this happens so they are aware when their
    setup is broken.
    
    ### Tests
    Added tests, tested locally.
  • feat(core): add structured network approval plumbing and policy decision model (#11672)
    ### Description
    #### Summary
    Introduces the core plumbing required for structured network approvals
    
    #### What changed
    - Added structured network policy decision modeling in core.
    - Added approval payload/context types needed for network approval
    semantics.
    - Wired shell/unified-exec runtime plumbing to consume structured
    decisions.
    - Updated related core error/event surfaces for structured handling.
    - Updated protocol plumbing used by core approval flow.
    - Included small CLI debug sandbox compatibility updates needed by this
    layer.
    
    #### Why
    establishes the minimal backend foundation for network approvals without
    yet changing high-level orchestration or TUI behavior.
    
    #### Notes
    - Behavior remains constrained by existing requirements/config gating.
    - Follow-up PRs in the stack handle orchestration, UX, and app-server
    integration.
    
    ---------
    
    Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
  • fix: send unfiltered models over model/list (#11793)
    ### What
    to unblock filtering models in VSCE, change `model/list` app-server
    endpoint to send all models + visibility field `showInPicker` so
    filtering can be done in VSCE if desired.
    
    ### Tests
    Updated tests.
  • codex-rs: fix thread resume rejoin semantics (#11756)
    ## Summary
    - always rejoin an in-memory running thread on `thread/resume`, even
    when overrides are present
    - reject `thread/resume` when `history` is provided for a running thread
    - reject `thread/resume` when `path` mismatches the running thread
    rollout path
    - warn (but do not fail) on override mismatches for running threads
    - add more `thread_resume` integration tests and fixes; including
    restart-based resume-with-overrides coverage
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-app-server --test all thread_resume`
    - manual test with app-server-test-client
    https://github.com/openai/codex/pull/11755
    - manual test both stdio and websocket in app
  • [app-server] add fuzzyFileSearch/sessionCompleted (#11773)
    this is to allow the client to know when to stop showing a spinner.
  • [apps] Improve app listing filtering. (#11697)
    - [x] If an installed app is not on the app listing, remove it from the
    final list.
  • [apps] Fix app loading logic. (#11518)
    When `app/list` is called with `force_refetch=True`, we should seed the
    results with what is already cached instead of starting from an empty
    list. Otherwise when we send app/list/updated events, the client will
    first see an empty list of accessible apps and then get the updated one.
  • Add cwd as an optional field to thread/list (#11651)
    Add's the ability to filter app-server thread/list by cwd
  • Persist complete TurnContextItem state via canonical conversion (#11656)
    ## Summary
    
    This PR delivers the first small, shippable step toward model-visible
    state diffing by making
    `TurnContextItem` more complete and standardizing how it is built.
    
    Specifically, it:
    - Adds persisted network context to `TurnContextItem`.
    - Introduces a single canonical `TurnContext -> TurnContextItem`
    conversion path.
    - Routes existing rollout write sites through that canonical conversion
    helper.
    
    No context injection/diff behavior changes are included in this PR.
    
    ## Why this change
    
    The design goal is to make `TurnContextItem` the canonical source of
    truth for context-diff
    decisions.
    Before this PR:
    - `TurnContextItem` did not include all TurnContext-derived environment
    inputs needed for v1
    completeness.
    - Construction was duplicated at multiple write sites.
    
    This PR addresses both with a minimal, reviewable change.
    
    ## Changes
    
    ### 1) Extend `TurnContextItem` with network state
    - Added `TurnContextNetworkItem { allowed_domains, denied_domains }`.
    - Added `network: Option<TurnContextNetworkItem>` to `TurnContextItem`.
    - Kept backward compatibility by making the new field optional and
    skipped when absent.
    
    Files:
    - `codex-rs/protocol/src/protocol.rs`
    
    ### 2) Canonical conversion helper
    - Added `TurnContext::to_turn_context_item(collaboration_mode)` in core.
    - Added internal helper to derive network fields from
    `config_layer_stack.requirements().network`.
    
    Files:
    - `codex-rs/core/src/codex.rs`
    
    ### 3) Use canonical conversion at rollout write sites
    - Replaced ad hoc `TurnContextItem { ... }` construction with
    `to_turn_context_item(...)` in:
      - sampling request path
      - compaction path
    
    Files:
    - `codex-rs/core/src/codex.rs`
    - `codex-rs/core/src/compact.rs`
    
    ### 4) Update fixtures/tests for new optional field
    - Updated existing `TurnContextItem` literals in tests to include
    `network: None`.
    - Added protocol tests for:
      - deserializing old payloads with no `network`
      - serializing when `network` is present
    
    Files:
    - `codex-rs/core/tests/suite/resume_warning.rs`
    - No replay/diff logic changes.
    - Persisted rollout `TurnContextItem` now carries additional network
    context when available.
    - Older rollout lines without `network` remain readable.
  • [apps] Add is_enabled to app info. (#11417)
    - [x] Add is_enabled to app info and the response of `app/list`.
    - [x] Update TUI to have Enable/Disable button on the app detail page.
  • fix(app-server): surface more helpful errors for json-rpc (#11638)
    Propagate client JSON-RPC errors for app-server request callbacks.
    Previously a number of possible errors were collapsed to `channel
    closed`. Now we should be able to see the underlying client error.
    
    ### Summary
    This change stops masking client JSON-RPC error responses as generic
    callback cancellation in app-server server->client request flows.
    
    Previously, when the client responded with a JSON-RPC error, we removed
    the callback entry but did not send anything to the waiting oneshot
    receiver. Waiters then observed channel closure (for example, auth
    refresh request canceled: channel closed), which hid the actual client
    error.
    
    Now, client JSON-RPC errors are forwarded through the callback channel
    and handled explicitly by request consumers.
    
    ### User-visible behavior
    - External auth refresh now surfaces real client JSON-RPC errors when
    provided.
    - True transport/callback-drop cases still report
    canceled/channel-closed semantics.
    
    ### Example: client JSON-RPC error is now propagated (not masked as
    "canceled")
    
    When app-server asks the client to refresh ChatGPT auth tokens, it sends
    a server->client JSON-RPC request like:
    
    ```json
    {
      "id": 42,
      "method": "account/chatgptAuthTokens/refresh",
      "params": {
        "reason": "unauthorized",
        "previousAccountId": "org-abc"
      }
    }
    ```
    
    If the client cannot refresh and responds with a JSON-RPC error:
    ```
    {
      "id": 42,
      "error": {
        "code": -32000,
        "message": "refresh failed",
        "data": null
      }
    }
    ```
    
    app-server now forwards that error through the callback path and
    surfaces:
    `auth refresh request failed: code=-32000 message=refresh failed`
    
    Previously, this same case could be reported as:
    `auth refresh request canceled: channel closed`
  • app-server: stabilize detached review start on Windows (#11646)
    ## Why
    
    `review_start_with_detached_delivery_returns_new_thread_id` has been
    failing on Windows CI. The failure mode is a process crash
    (`tokio-runtime-worker` stack overflow) during detached review setup,
    which causes EOF in the test harness.
    
    This test is intended to validate detached review thread identity, not
    shell snapshot behavior. We also still want detached review to avoid
    unnecessary rollout-path rediscovery when the parent thread is already
    loaded.
    
    ## What Changed
    
    - Updated detached review startup in
    `codex-rs/app-server/src/codex_message_processor.rs`:
      - `start_detached_review` now receives the loaded parent thread.
      - It prefers `parent_thread.rollout_path()`.
    - It falls back to `find_thread_path_by_id_str(...)` only if the
    in-memory path is unavailable.
    - Hardened the review test fixture in
    `codex-rs/app-server/tests/suite/v2/review.rs` by setting
    `shell_snapshot = false` in test config, so this test no longer depends
    on unrelated Windows PowerShell snapshot initialization.
    
    ## Verification
    
    - `cargo test -p codex-app-server`
    - Verified
    `suite::v2::review::review_start_with_detached_delivery_returns_new_thread_id`
    passes locally.
    
    ## Notes
    
    - Related context: rollout-path lookup behavior changed in #10532.
  • app-server tests: disable shell_snapshot for review suite (#11657)
    ## Why
    
    
    `suite::v2::review::review_start_with_detached_delivery_returns_new_thread_id`
    was failing on Windows CI due to an unrelated process crash during shell
    snapshot initialization (`tokio-runtime-worker` stack overflow).
    
    This review test suite validates review API behavior and should not
    depend on shell snapshot behavior. Keeping shell snapshot enabled in
    this fixture made the test flaky for reasons outside the scenario under
    test.
    
    ## What Changed
    
    - Updated the review suite test config in
    `codex-rs/app-server/tests/suite/v2/review.rs` to set:
      - `shell_snapshot = false`
    
    This keeps the review tests focused on review behavior by disabling
    shell snapshot initialization in this fixture.
    
    ## Verification
    
    - `cargo test -p codex-app-server`
    - Confirmed the previously failing Windows CI job for this test now
    passes on this PR.
  • feat: introduce Permissions (#11633)
    ## Why
    We currently carry multiple permission-related concepts directly on
    `Config` for shell/unified-exec behavior (`approval_policy`,
    `sandbox_policy`, `network`, `shell_environment_policy`,
    `windows_sandbox_mode`).
    
    Consolidating these into one in-memory struct makes permission handling
    easier to reason about and sets up the next step: supporting named
    permission profiles (`[permissions.PROFILE_NAME]`) without changing
    behavior now.
    
    This change is mostly mechanical: it updates existing callsites to go
    through `config.permissions`, but it does not yet refactor those
    callsites to take a single `Permissions` value in places where multiple
    permission fields are still threaded separately.
    
    This PR intentionally **does not** change the on-disk `config.toml`
    format yet and keeps compatibility with legacy config keys.
    
    ## What Changed
    - Introduced `Permissions` in `core/src/config/mod.rs`.
    - Added `Config::permissions` and moved effective runtime permission
    fields under it:
      - `approval_policy`
      - `sandbox_policy`
      - `network`
      - `shell_environment_policy`
      - `windows_sandbox_mode`
    - Updated config loading/building so these effective values are still
    derived from the same existing config inputs and constraints.
    - Updated Windows sandbox helpers/resolution to read/write via
    `permissions`.
    - Threaded the new field through all permission consumers across core
    runtime, app-server, CLI/exec, TUI, and sandbox summary code.
    - Updated affected tests to reference `config.permissions.*`.
    - Renamed the struct/field from
    `EffectivePermissions`/`effective_permissions` to
    `Permissions`/`permissions` and aligned variable naming accordingly.
    
    ## Verification
    - `just fix -p codex-core -p codex-tui -p codex-cli -p codex-app-server
    -p codex-exec -p codex-utils-sandbox-summary`
    - `cargo build -p codex-core -p codex-tui -p codex-cli -p
    codex-app-server -p codex-exec -p codex-utils-sandbox-summary`
  • feat(app-server): experimental flag to persist extended history (#11227)
    This PR adds an experimental `persist_extended_history` bool flag to
    app-server thread APIs so rollout logs can retain a richer set of
    EventMsgs for non-lossy Thread > Turn > ThreadItems reconstruction (i.e.
    on `thread/resume`).
    
    ### Motivation
    Today, our rollout recorder only persists a small subset (e.g. user
    message, reasoning, assistant message) of `EventMsg` types, dropping a
    good number (like command exec, file change, etc.) that are important
    for reconstructing full item history for `thread/resume`, `thread/read`,
    and `thread/fork`.
    
    Some clients want to be able to resume a thread without lossiness. This
    lossiness is primarily a UI thing, since what the model sees are
    `ResponseItem` and not `EventMsg`.
    
    ### Approach
    This change introduces an opt-in `persist_full_history` flag to preserve
    those events when you start/resume/fork a thread (defaults to `false`).
    
    This is done by adding an `EventPersistenceMode` to the rollout
    recorder:
    - `Limited` (existing behavior, default)
    - `Extended` (new opt-in behavior)
    
    In `Extended` mode, persist additional `EventMsg` variants needed for
    non-lossy app-server `ThreadItem` reconstruction. We now store the
    following ThreadItems that we didn't before:
    - web search
    - command execution
    - patch/file changes
    - MCP tool calls
    - image view calls
    - collab tool outcomes
    - context compaction
    - review mode enter/exit
    
    For **command executions** in particular, we truncate the output using
    the existing `truncate_text` from core to store an upper bound of 10,000
    bytes, which is also the default value for truncating tool outputs shown
    to the model. This keeps the size of the rollout file and command
    execution items returned over the wire reasonable.
    
    And we also persist `EventMsg::Error` which we can now map back to the
    Turn's status and populates the Turn's error metadata.
    
    #### Updates to EventMsgs
    To truly make `thread/resume` non-lossy, we also needed to persist the
    `status` on `EventMsg::CommandExecutionEndEvent` and
    `EventMsg::PatchApplyEndEvent`. Previously it was not obvious whether a
    command failed or was declined (similar for apply_patch). These
    EventMsgs were never persisted before so I made it a required field.
  • chore: drop mcp validation of dynamic tools (#11609)
    Drop validation of dynamic tools using MCP names to reduce latency
  • feat: make sandbox read access configurable with ReadOnlyAccess (#11387)
    `SandboxPolicy::ReadOnly` previously implied broad read access and could
    not express a narrower read surface.
    This change introduces an explicit read-access model so we can support
    user-configurable read restrictions in follow-up work, while preserving
    current behavior today.
    
    It also ensures unsupported backends fail closed for restricted-read
    policies instead of silently granting broader access than intended.
    
    ## What
    
    - Added `ReadOnlyAccess` in protocol with:
      - `Restricted { include_platform_defaults, readable_roots }`
      - `FullAccess`
    - Updated `SandboxPolicy` to carry read-access configuration:
      - `ReadOnly { access: ReadOnlyAccess }`
      - `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }`
    - Preserved existing behavior by defaulting current construction paths
    to `ReadOnlyAccess::FullAccess`.
    - Threaded the new fields through sandbox policy consumers and call
    sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and
    related tests.
    - Updated Seatbelt policy generation to honor restricted read roots by
    emitting scoped read rules when full read access is not granted.
    - Added fail-closed behavior on Linux and Windows backends when
    restricted read access is requested but not yet implemented there
    (`UnsupportedOperation`).
    - Regenerated app-server protocol schema and TypeScript artifacts,
    including `ReadOnlyAccess`.
    
    ## Compatibility / rollout
    
    - Runtime behavior remains unchanged by default (`FullAccess`).
    - API/schema changes are in place so future config wiring can enable
    restricted read access without another policy-shape migration.
  • test(app-server): stabilize app/list thread feature-flag test by using file-backed MCP OAuth creds (#11521)
    ## Why
    
    `suite::v2::app_list::list_apps_uses_thread_feature_flag_when_thread_id_is_provided`
    has been flaky in CI. The test exercises `thread/start`, which
    initializes `codex_apps`. In CI/Linux, that path can reach OS
    keyring-backed MCP OAuth credential lookup (`Codex MCP Credentials`) and
    intermittently abort the MCP process (observed stack overflow in
    `zbus`), causing the test to fail before the assertion logic runs.
    
    ## What Changed
    
    - Updated the test config in
    `codex-rs/app-server/tests/suite/v2/app_list.rs` to set
    `mcp_oauth_credentials_store = "file"` in both relevant config-writing
    paths:
    - The in-test config override inside
    `list_apps_uses_thread_feature_flag_when_thread_id_is_provided`
    - `write_connectors_config(...)`, which is used by the v2 `app_list`
    test suite
    - This keeps test coverage focused on thread-scoped app feature flags
    while removing OS keyring/DBus dependency from this test path.
    
    ## How It Was Verified
    
    - `cargo test -p codex-app-server`
    - `cargo test -p codex-app-server
    list_apps_uses_thread_feature_flag_when_thread_id_is_provided --
    --nocapture`
  • app-server: thread resume subscriptions (#11474)
    This stack layer makes app-server thread event delivery connection-aware
    so resumed/attached threads only emit notifications and approval prompts
    to subscribed connections.
    
    - Added per-thread subscription tracking in `ThreadState`
    (`subscribed_connections`) and mapped subscription ids to `(thread_id,
    connection_id)`.
    - Updated listener lifecycle so removing a subscription or closing a
    connection only removes that connection from the thread’s subscriber
    set; listener shutdown now happens when the last subscriber is gone.
    - Added `connection_closed(connection_id)` plumbing (`lib.rs` ->
    `message_processor.rs` -> `codex_message_processor.rs`) so disconnect
    cleanup happens immediately.
    - Scoped bespoke event handling outputs through `TargetedOutgoing` to
    send requests/notifications only to subscribed connections.
    - Kept existing threadresume behavior while aligning with the latest
    split-loop transport structure.
  • refactor: codex app-server ThreadState (#11419)
    this is a no-op functionality wise. consolidates thread-specific message
    processor / event handling state in ThreadState
  • Promote Windows Sandbox (#11341)
    1. Move Windows Sandbox NUX to right after trust directory screen
    2. Don't offer read-only as an option in Sandbox NUX.
    Elevated/Legacy/Quit
    3. Don't allow new untrusted directories. It's trust or quit
    4. move experimental sandbox features to `[windows]
    sandbox="elevated|unelevatd"`
    5. Copy tweaks = elevated -> default, non-elevated -> non-admin
  • Reapply "Add app-server transport layer with websocket support" (#11370)
    Reapply "Add app-server transport layer with websocket support" with
    additional fixes from https://github.com/openai/codex/pull/11313/changes
    to avoid deadlocking.
    
    This reverts commit 47356ff83c.
    
    ## Summary
    
    To avoid deadlocking when queues are full, we maintain separate tokio
    tasks dedicated to incoming vs outgoing event handling
    - split the app-server main loop into two tasks in
    `run_main_with_transport`
       - inbound handling (`transport_event_rx`)
       - outbound handling (`outgoing_rx` + `thread_created_rx`)
    - separate incoming and outgoing websocket tasks
    
    ## Validation
    
    Integration tests, testing thoroughly e2e in codex app w/ >10 concurrent
    requests
    
    <img width="1365" height="979" alt="Screenshot 2026-02-10 at 2 54 22 PM"
    src="https://github.com/user-attachments/assets/47ca2c13-f322-4e5c-bedd-25859cbdc45f"
    />
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Cache cloud requirements (#11305)
    We're loading these from the web on every startup. This puts them in a
    local file with a 1hr TTL.
    
    We sign the downloaded requirements with a key compiled into the Codex
    CLI to prevent unsophisticated tampering (determined circumvention is
    outside of our threat model: after all, one could just compile Codex
    without any of these checks).
    
    If any of the following are true, we ignore the local cache and re-fetch
    from Cloud:
    * The signature is invalid for the payload (== requirements, sign time,
    ttl, user identity)
    * The identity does not match the auth'd user's identity
    * The TTL has expired
    * We cannot parse requirements.toml from the payload
  • feat: split codex-common into smaller utils crates (#11422)
    We are removing feature-gated shared crates from the `codex-rs`
    workspace. `codex-common` grouped several unrelated utilities behind
    `[features]`, which made dependency boundaries harder to reason about
    and worked against the ongoing effort to eliminate feature flags from
    workspace crates.
    
    Splitting these utilities into dedicated crates under `utils/` aligns
    this area with existing workspace structure and keeps each dependency
    explicit at the crate boundary.
    
    ## What changed
    
    - Removed `codex-rs/common` (`codex-common`) from workspace members and
    workspace dependencies.
    - Added six new utility crates under `codex-rs/utils/`:
      - `codex-utils-cli`
      - `codex-utils-elapsed`
      - `codex-utils-sandbox-summary`
      - `codex-utils-approval-presets`
      - `codex-utils-oss`
      - `codex-utils-fuzzy-match`
    - Migrated the corresponding modules out of `codex-common` into these
    crates (with tests), and added matching `BUILD.bazel` targets.
    - Updated direct consumers to use the new crates instead of
    `codex-common`:
      - `codex-rs/cli`
      - `codex-rs/tui`
      - `codex-rs/exec`
      - `codex-rs/app-server`
      - `codex-rs/mcp-server`
      - `codex-rs/chatgpt`
      - `codex-rs/cloud-tasks`
    - Updated workspace lockfile entries to reflect the new dependency graph
    and removal of `codex-common`.
  • Remove test-support feature from codex-core and replace it with explicit test toggles (#11405)
    ## Why
    
    `codex-core` was being built in multiple feature-resolved permutations
    because test-only behavior was modeled as crate features. For a large
    crate, those permutations increase compile cost and reduce cache reuse.
    
    ## Net Change
    
    - Removed the `test-support` crate feature and related feature wiring so
    `codex-core` no longer needs separate feature shapes for test consumers.
    - Standardized cross-crate test-only access behind
    `codex_core::test_support`.
    - External test code now imports helpers from
    `codex_core::test_support`.
    - Underlying implementation hooks are kept internal (`pub(crate)`)
    instead of broadly public.
    
    ## Outcome
    
    - Fewer `codex-core` build permutations.
    - Better incremental cache reuse across test targets.
    - No intended production behavior change.
  • feat: support multiple rate limits (#11260)
    Added multi-limit support end-to-end by carrying limit_name in
    rate-limit snapshots and handling multiple buckets instead of only
    codex.
    Extended /usage client parsing to consume additional_rate_limits
    Updated TUI /status and in-memory state to store/render per-limit
    snapshots
    Extended app-server rate-limit read response: kept rate_limits and added
    rate_limits_by_name.
    Adjusted usage-limit error messaging for non-default codex limit buckets
  • chore: persist turn_id in rollout session and make turn_id uuid based (#11246)
    Problem:
    1. turn id is constructed in-memory;
    2. on resuming threads, turn_id might not be unique;
    3. client cannot no the boundary of a turn from rollout files easily.
    
    This PR does three things:
    1. persist `task_started` and `task_complete` events;
    1. persist `turn_id` in rollout turn events;
    5. generate turn_id as unique uuids instead of incrementing it in
    memory.
    
    This helps us resolve the issue of clients wanting to have unique turn
    ids for resuming a thread, and knowing the boundry of each turn in
    rollout files.
    
    example debug logs
    ```
    2026-02-11T00:32:10.746876Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=8 turn=Turn { id: "019c4a07-d809-74c3-bc4b-fd9618487b4b", items: [UserMessage { id: "item-24", content: [Text { text: "hi", text_elements: [] }] }, AgentMessage { id: "item-25", text: "Hi. I’m in the workspace with your current changes loaded and ready. Send the next task and I’ll execute it end-to-end." }], status: Completed, error: None }
    2026-02-11T00:32:10.746888Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=9 turn=Turn { id: "019c4a18-1004-76c0-a0fb-a77610f6a9b8", items: [UserMessage { id: "item-26", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-27", text: "Hello. Ready for the next change in `codex-rs`; I can continue from the current in-progress diff or start a new task." }], status: Completed, error: None }
    2026-02-11T00:32:10.746899Z DEBUG codex_app_server_protocol::protocol::thread_history: built turn from rollout items turn_index=10 turn=Turn { id: "019c4a19-41f0-7db0-ad78-74f1503baeb8", items: [UserMessage { id: "item-28", content: [Text { text: "hello", text_elements: [] }] }, AgentMessage { id: "item-29", text: "Hello. Send the specific change you want in `codex-rs`, and I’ll implement it and run the required checks." }], status: Completed, error: None }
    ```
    
    backward compatibility:
    if you try to resume an old session without task_started and
    task_complete event populated, the following happens:
    - If you resume and do nothing: those reconstructed historical IDs can
    differ next time you resume.
    - If you resume and send a new turn: the new turn gets a fresh UUID from
    live submission flow and is persisted, so that new turn’s ID is stable
    on later resumes.
    I think this behavior is fine, because we only care about deterministic
    turn id once a turn is triggered.