Commit Graph

1295 Commits

  • [codex] Add model tool mode selector (#25031)
    ## Why
    Some models need to select their code-execution behavior through model
    catalog metadata. Models without that metadata must continue to follow
    the existing `CodeMode` and `CodeModeOnly` feature flags, including when
    a newer server sends an enum value this client does not recognize.
    
    ## What changed
    - add optional `ModelInfo.tool_mode` metadata with `direct`,
    `code_mode`, and `code_mode_only`
    - treat omitted and unknown wire values as `None`
    - resolve `None` from the existing feature flags
    - carry the resolved `ToolMode` directly on `TurnContext`, outside
    `Config`
    - use the resolved value for turn creation, model switches, review
    turns, tool planning, and code execution
    
    ## Coverage
    - add protocol coverage for omitted, known, and unknown enum values
    - add focused coverage for flag fallback and explicit metadata
    overriding feature flags
    - add core integration coverage that fetches remote model metadata
    through `/v1/models` and verifies the outbound `/responses` tools for
    explicit `direct` and `code_mode_only` selectors
    
    ## Stack
    - followed by #25032
  • [codex] Improve built-in tool schema docs (#24794)
    ## Summary
    - Clarify default, omission, and bounded behavior across built-in tool
    schemas, including unified exec, classic shell, Code Mode exec/wait,
    multi-agent, agent job, MCP resource, image, goal, plan, tool_search,
    and test-sync fields.
    - Convert update_plan status to an enum and add short field descriptions
    where the schema previously relied on surrounding context.
    - Remove the dedicated permission-approval schema test and keep only
    updates to existing expected-spec tests.
    
    ## Validation
    - Ran `just fmt`.
    - Ran `git diff --check`.
    - Did not run clippy or tests, per request.
    
    Regression has been eval
    [here](https://openai.slack.com/archives/C09GDSP1J9X/p1779905065496949)
    and we proved there are no regressions
  • fix: preserve deny-read sandboxing for safe commands (#23943)
    ## Why
    
    Permission profiles can mark filesystem entries as unreadable with
    `deny` rules, including glob patterns. Several shell execution paths
    treated known-safe commands or execpolicy `allow` rules as sufficient to
    run outside the filesystem sandbox. That is not valid for read-capable
    commands: for example, `cat` or `ls` may be reasonable to allow
    generally, but dropping the sandbox would also drop deny-read
    constraints such as `**/*.env`.
    
    ## What changed
    
    - Added a shared check that treats active deny-read restrictions as
    incompatible with unsandboxed execution.
    - Kept first-attempt execution sandboxed for explicit escalation and
    execpolicy allow bypasses when deny-read entries are present.
    - Prevented no-sandbox retry after a sandbox denial when the active
    filesystem policy contains deny-read entries.
    - Updated the zsh-fork execve path so prefix-rule `allow` decisions
    continue inside the current sandbox when deny-read restrictions are
    active.
    
    ## Verification
    
    - `cargo test -p codex-core tools::sandboxing::tests`
    - `cargo test -p codex-core
    tools::runtimes::shell::unix_escalation::tests`
    - `cargo test -p codex-core
    shell_command_enforces_glob_deny_read_policy`
  • windows-sandbox: pass workspace roots to runner (#24108)
    ## Why
    
    #23813 switches the Windows sandbox runner path to `PermissionProfile`,
    but it still left one runtime anchor for resolving symbolic
    `:workspace_roots` entries. That is not enough once a turn has multiple
    effective workspace roots: exact entries and deny globs under
    `:workspace_roots` need to be materialized for every runtime root before
    the command runner chooses token mode or builds ACL plans.
    
    ## What Changed
    
    - Replaces the Windows runner/setup `permission_profile_cwd` plumbing
    with `workspace_roots: Vec<AbsolutePathBuf>`.
    - Resolves Windows-local `PermissionProfile` data with
    `materialize_project_roots_with_workspace_roots(...)` instead of the
    single-cwd helper.
    - Threads `Config::effective_workspace_roots()` through core execution,
    unified exec, TUI setup/read-grant flows, app-server setup, app-server
    `command/exec`, and `debug sandbox` on Windows.
    - Preserves those workspace roots through the zsh-fork escalation
    executor instead of rebuilding them from `sandbox_policy_cwd`.
    - Makes `ExecRequest::new(...)` and the remaining
    `build_exec_request(...)` helper path take
    `windows_sandbox_workspace_roots` explicitly so new call sites cannot
    silently fall back to `vec![cwd]`.
    - Clarifies the `debug sandbox` non-Windows comment: remaining
    cwd-dependent resolution still uses `sandbox_policy_cwd`, while
    `:workspace_roots` entries are already materialized from config roots.
    - Updates elevated runner IPC `SpawnRequest` to send `workspace_roots`
    and bumps the framed IPC protocol version to `3` for the payload shape
    change.
    - Adds Windows-local resolver coverage for expanding exact and glob
    `:workspace_roots` entries across multiple roots, plus core helper
    coverage proving explicit roots are preserved.
    
    ## Verification
    
    - `cargo check -p codex-windows-sandbox -p codex-core -p codex-tui -p
    codex-cli -p codex-app-server`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-core unix_escalation`
    - `cargo test -p codex-app-server windows_sandbox`
    - `cargo test -p codex-tui windows_sandbox`
    - `cargo test -p codex-cli debug_sandbox`
    - `just test -p codex-core unified_exec`
    - `just test -p codex-core
    build_exec_request_preserves_windows_workspace_roots`
    - `env -u CODEX_NETWORK_PROXY_ACTIVE -u
    CODEX_NETWORK_ALLOW_LOCAL_BINDING just test -p codex-app-server --lib
    command_exec`
    - `just test -p codex-windows-sandbox`
    - `just test -p codex-exec sandbox`
    - `just fix -p codex-core -p codex-app-server -p codex-windows-sandbox`
    
    A local macOS cross-check with `cargo check --target
    x86_64-pc-windows-msvc ...` did not reach crate Rust code because native
    dependencies require Windows SDK headers (`windows.h` / `assert.h`) in
    this environment; Windows CI remains the real target validation.
    
    Two local targeted filters compile but do not run assertions on macOS:
    `env -u CODEX_NETWORK_PROXY_ACTIVE -u CODEX_NETWORK_ALLOW_LOCAL_BINDING
    just test -p codex-app-server --lib command_exec_processor` matched zero
    tests, and `just test -p codex-linux-sandbox landlock` matched zero
    tests because the landlock suite is Linux-only.
  • Surface filesystem permission profiles in prompt context (#23924)
    ## Summary
    Some permission profiles can encode filesystem reads that should remain
    unavailable to the agent. Before this change, the model-visible context
    and automatic approval review prompt summarized the effective
    permissions as a legacy sandbox mode, which can omit permission-profile
    filesystem entries from escalation decisions.
    
    For example, a profile can grant workspace access while denying a
    private subtree across every workspace root:
    
    ```toml
    default_permissions = "restricted-workspace"
    
    [permissions.restricted-workspace.workspace_roots]
    "/Users/alice/project" = true
    "/Users/alice/other-project" = true
    
    [permissions.restricted-workspace.filesystem]
    ":minimal" = "read"
    
    [permissions.restricted-workspace.filesystem.":workspace_roots"]
    "." = "write"
    "private" = "deny"
    "private/**" = "deny"
    ```
    
    The context window now describes the workspace roots and effective
    filesystem side of the `PermissionProfile` directly, with deny entries
    marked as non-escalatable:
    
    ```xml
    <environment_context>
      <cwd>/Users/alice/project</cwd>
      <shell>zsh</shell>
      <filesystem><workspace_roots><root>/Users/alice/project</root><root>/Users/alice/other-project</root></workspace_roots><permission_profile type="managed"><file_system type="restricted"><entry access="read"><special>:minimal</special></entry><entry access="write"><path>/Users/alice/project</path></entry><entry access="write"><path>/Users/alice/other-project</path></entry><entry access="deny" escalatable="false"><path>/Users/alice/project/private</path></entry><entry access="deny" escalatable="false"><path>/Users/alice/other-project/private</path></entry><entry access="deny" escalatable="false"><glob>/Users/alice/project/private/**</glob></entry><entry access="deny" escalatable="false"><glob>/Users/alice/other-project/private/**</glob></entry></file_system></permission_profile></filesystem>
    </environment_context>
    ```
    
    Managed requirements can impose the same kind of deny-read restriction:
    
    ```toml
    [permissions.filesystem]
    deny_read = [
      "/Users/alice/project/private",
      "/Users/alice/project/private/**",
    ]
    ```
    
    The automatic approval review prompt also receives the parent turn's
    denied-read context, so review decisions can account for the active
    permission profile.
    
    ## What Changed
    - Render the effective filesystem profile in `<environment_context>`,
    including profile type, filesystem entries, workspace roots, and
    non-escalatable deny entries.
    - Persist effective `workspace_roots` in `TurnContextItem` so
    resumed/replayed context does not have to bind `:workspace_roots`
    through legacy `cwd` fallback.
    - Add explicit permission instructions that denied reads are policy
    restrictions, not escalation targets.
    - Pass the parent turn's denied-read context into automatic approval
    reviews.
    - Add targeted coverage for prompt rendering, workspace-root
    materialization, replay context, and review prompt context.
    - Keep the prompt-context test expectations platform-aware so the same
    filesystem rendering assertions pass on Unix and Windows paths.
    
    ## Testing
    - `just test -p codex-core
    context::environment_context::tests::serialize_environment_context_with_full_filesystem_profile`
    - `just test -p codex-core
    context::environment_context::tests::turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd`
    - `just test -p codex-core
    context::permissions_instructions::permissions_instructions_tests::builds_permissions_from_profile_with_denied_reads`
    - `just fix -p codex-core`
    
    I also attempted `just test -p codex-core`; the changed prompt-context
    tests passed, but the full local run did not complete cleanly in this
    sandboxed macOS environment due unrelated user-shell `CODEX_SANDBOX*`
    expectations and integration-test timeouts.
  • [codex] Add user input client ids (#24653)
    ## Summary
    
    Adds an optional `clientId` field to app-server v2 `UserInput` and
    carries it through the core `UserInput` model so clients can correlate
    echoed user input items without relying on payload equality.
    
    ## Details
    
    - Adds `client_id: Option<String>` to core `UserInput` variants.
    - Exposes the v2 app-server field as `clientId` on the wire and in
    generated TypeScript.
    - Preserves the id when converting between app-server v2 and core
    protocol types.
    - Regenerates app-server schema fixtures.
    
    ## Validation
    
    - `just fmt`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-protocol`
    - `git diff --check`
  • runtime: prepend zsh fork bin dir to PATH (#23768)
    ## Why
    
    #23756 makes packaged Codex builds include and default to the bundled
    zsh fork. The important reason to put that fork's directory at the front
    of `PATH` is to keep executable-level escalation working after a command
    leaves the original shell and later re-enters zsh through `env`.
    
    The expected chain is:
    
    1. The zsh fork runs the top-level shell command.
    2. That command launches another program, such as `python3`, while
    inheriting the `EXEC_WRAPPER` environment and the escalation socket fd.
    3. That program spawns a shell script whose shebang is `#!/usr/bin/env
    zsh` rather than `#!/bin/zsh`, and it does not close the escalation fd.
    4. `/usr/bin/env` resolves `zsh` through `PATH`, so it must find the
    packaged zsh fork before the system zsh.
    5. Commands inside that nested script are intercepted by the zsh fork
    and can still request escalation from Codex.
    
    If `PATH` resolves `zsh` to the system shell instead, the nested script
    loses zsh-fork exec interception. Commands that should request
    escalation can then run only in the original sandbox, or fail there,
    without Codex ever receiving the approval request.
    
    Shell snapshots make this slightly more subtle: a snapshot can restore
    an older `PATH` after the child shell starts. This PR treats the zsh
    fork `PATH` prepend as an explicit environment override so snapshot
    wrapping preserves it.
    
    ## What Changed
    
    - Added shared zsh-fork runtime helpers that prepend the configured zsh
    executable parent directory to `PATH` without duplicate entries.
    - Applied the zsh fork `PATH` prepend to both zsh-fork `shell_command`
    launches and unified-exec zsh-fork launches before sandbox command
    construction.
    - Kept the shell-command zsh-fork backend API narrow: it derives the
    configured zsh path from session services and rebuilds its sandbox
    environment from `req.env`, rather than accepting a second, competing
    environment map or a separately threaded bin dir.
    - Kept Unix-only zsh-fork `PATH` mutation out of Windows clippy-visible
    mutability.
    - Added coverage for duplicate `PATH` entries, for preserving the zsh
    fork prepend through shell snapshot wrapping, and for the nested
    `python3` -> `#!/usr/bin/env zsh` escalation flow.
    
    ## Testing
    
    - `just fmt`
    - `just fix -p codex-core`
    
    I left final test validation to CI after the latest review-comment
    cleanup. Before that cleanup, `just test -p codex-core zsh_fork` passed
    locally for the zsh-fork-focused tests.
  • [codex] Remove redundant SQLite dynamic tool storage (#24819)
    ## Why
    
    Dynamic tools are defined at thread start and already stored in rollout
    `SessionMeta`, which restores resumed and forked sessions. Persisting
    the same tools through SQLite creates a second runtime persistence path
    that is unnecessary prework for the explicit namespace refactor.
    
    ## What changed
    
    - Restore missing thread-start dynamic tools directly from rollout
    history, including when SQLite is enabled.
    - Remove SQLite dynamic-tool reads, writes, backfill, and thread
    metadata patch plumbing.
    - Add SQLite-enabled resume integration coverage that verifies a
    rollout-defined dynamic tool is still sent after resume.
    
    ## Compatibility
    
    The existing `thread_dynamic_tools` table is intentionally not dropped
    even though it's now unused. Older Codex binaries are allowed to open
    databases migrated by newer binaries and still reference this table;
    dropping it would break that mixed-version path. See
    [here](https://github.com/openai/codex/blob/main/codex-rs/state/src/migrations.rs#L10-L11).
    
    ## Verification
    
    - `just test -p codex-state -p codex-rollout -p codex-thread-store`
    - `just test -p codex-core --test all
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
  • [codex] add compaction metadata to turn headers (#24368)
    ## Summary
    - Add `request_kind` values for foreground turn, startup prewarm,
    compaction, and detached memory model requests.
    - Attach compaction dispatch metadata to local Responses, legacy
    `/v1/responses/compact`, and remote v2 compact requests.
    - Add the existing logical context-window identifier as `window_id` on
    turn-owned model request metadata.
    - Keep identity fields optional for detached memory requests, while
    still emitting `request_kind="memory"` in non-git/no-sandbox workspaces.
    
    ## Root Cause
    `x-codex-turn-metadata` has more than one producer. Foreground turns and
    compaction requests own a real turn and should carry that turn identity.
    Detached memory stage-one requests do not own a foreground turn, so
    absent identity fields are valid rather than missing data. Startup
    websocket prewarm is also a model request, but it has `generate=false`
    and must not be counted as a foreground turn.
    
    `thread_source` or session source identifies where a thread came from
    (for example review, guardian, or another subagent). `request_kind`
    identifies what the current outbound model request is doing (`turn`,
    `prewarm`, `compaction`, or `memory`). A review or guardian thread can
    issue either a normal turn request or a compaction request, so source
    cannot replace request kind.
    
    ## Behavior / Impact
    - Ordinary foreground requests send `request_kind="turn"`, their real
    identity fields, and `window_id="<thread_id>:<window_generation>"`.
    - Startup websocket warmup requests send `request_kind="prewarm"` so
    they are not counted as foreground turns.
    - Compaction requests send `request_kind="compaction"`, their real
    owning turn identity, the existing `window_id`, and
    `compaction.{trigger,reason,implementation,phase,strategy}`.
    - Detached memory stage-one requests send `request_kind="memory"`
    without `session_id`, `thread_id`, `turn_id`, or `window_id`; when no
    workspace metadata exists, the kind-only header is still emitted.
    - `session_id`, `thread_id`, `turn_id`, and `window_id` remain optional
    in the header schema because detached memory requests do not own a
    foreground turn or context window.
    - `window_id` is not a new ID system: it is copied from the already-sent
    `x-codex-window-id` / WS client metadata value at model-request dispatch
    time.
    - Existing `x-codex-window-id` HTTP/WS emission, value format,
    generation advancement, resume behavior, and fork reset behavior are
    unchanged.
    - `request_kind`, `window_id`, and upstream turn-owned identity fields
    remain schema-owned; input `responsesapi_client_metadata` cannot replace
    their canonical values.
    - No table, DAG, export, app-server API, or MCP `_meta` schema changes
    are included.
    
    A compaction attempt stopped by a pre-compact hook issues no model
    request and therefore has no request header; its outcome remains in
    analytics events. Status, error, duration, and token deltas also remain
    analytics fields rather than request-header fields.
    
    Future detached-memory attribution using a real initiating turn ID as
    `trigger_turn_id` is intentionally not part of this PR.
    
    ## Sync With Main
    - Final pushed head `716342e79` is rebased onto `origin/main@0d37db4b2`.
    - The metadata conflict came from upstream `#24160`, which added
    `forked_from_thread_id` on the same `turn_metadata` surface. Resolution
    preserves that field and its protection from client metadata override
    alongside this PR's request-kind, compaction, and window-id fields.
    - While resolving the overlapping commits, I removed an accidental
    recursive model-request overlay and a duplicate detached-memory header
    builder before completing the rebase.
    
    ## Latency / User Experience Boundary
    - Foreground turns perform no new filesystem, git, or network work. New
    fields are inserted into metadata already serialized for outgoing
    requests.
    - Compaction issues the same model/HTTP requests with the same prompt,
    model, service tier, and sampling settings; only metadata bytes change.
    - Startup prewarm already sent metadata; it is now correctly classified
    as `prewarm`.
    - Non-git detached memory now sends a small kind-only metadata header
    rather than no header.
    - This client diff adds no user-visible latency mechanism beyond
    negligible serialization and header bytes on already-existing requests.
    
    ## Validation
    On conflict-resolved head `1d35c2cfb` based on `origin/main@487521733`:
    - `just fmt` (passed)
    - `just fix -p codex-core` (passed)
    - `git diff --check origin/main...HEAD` (passed)
    - `just test -p codex-core -E 'test(turn_metadata) |
    test(websocket_first_turn_uses_startup_prewarm_and_create) |
    test(responses_stream_includes_turn_metadata_header_for_git_workspace_e2e)
    |
    test(responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create)
    | test(remote_compact_v2_retries_failures_with_stream_retry_budget) |
    test(window_id_advances_after_compact_persists_on_resume_and_resets_on_fork)'`
    (`23 passed`; `bench-smoke` passed)
    - `just test -p codex-app-server -E
    'test(turn_start_forwards_client_metadata_to_responses_request_v2) |
    test(turn_start_forwards_client_metadata_to_responses_websocket_request_body_v2)
    | test(auto_compaction_remote_emits_started_and_completed_items)'` (`3
    passed`; `bench-smoke` passed)
    - `just test -p codex-memories-write` (`29 passed`; `bench-smoke`
    passed)
  • Fix guardian review test user input (#24746)
    ## Summary
    - Add the missing additional_context field to the guardian review
    Op::UserInput test initializer.
    
    ## Test plan
    - just fmt
    - just test -p codex-core guardian_review
    - just test -p codex-core (compiles, then fails on local environment
    issues: sandbox-exec Operation not permitted, missing test_stdio_server
    helper binary, and unrelated timeouts)
  • fix(auto-review) skip legacy notify for auto review threads (#24714)
    ## Summary
    Clear inherited legacy `notify` from Guardian review session config,
    since we should not be passing auto review threads into `notify`
    targets. Keeps legacy notify payload and hook runtime behavior unchanged
    for normal user turns.
    
    ## Testing
    - [x] add a Guardian config regression and dedicated Guardian
    integration test so review sessions cannot inherit parent notify hooks
  • Uprev Rust toolchain pins to 1.95.0 (#24684)
    ## Summary
    - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
    Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
    environment config.
    - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
    match the new version.
    - Leave purpose-specific toolchains unchanged, including the
    `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
    build pin.
    - Includes fixes for new lints from `just fix` and a few codex-authored
    fixes for lints without a suggestion.
  • [codex] remove plain image wrapper spans (#24652)
    ## Why
    
    Remote image submissions currently wrap native `input_image` spans in
    literal `<image>` and `</image>` text spans. Those extra prompt tokens
    add structure without providing label or routing information.
    
    ## What Changed
    
    - Serialize `UserInput::Image` directly as an `input_image` content
    span.
    - Preserve named local-image framing and legacy wrapper parsing for
    labeled attachments and existing histories.
    - Update existing request-shape expectations for drag-and-drop images,
    model switching, and compaction.
    
    ## Validation
    
    - `just test -p codex-protocol`
    - Focused `codex-core` run covering
    `drag_drop_image_persists_rollout_request_shape`,
    `model_change_from_image_to_text_strips_prior_image_content`, and
    `snapshot_request_shape_pre_turn_compaction_including_incoming_user_message`
    
    ## Notes
    
    - A broader `just test -p codex-core` run was attempted; the affected
    tests passed, while the overall run failed in unrelated CLI, MCP, and
    tooling tests plus a `thread_manager` timeout.
  • Add forked_from_thread_id turn metadata (#24160)
    ## Why
    
    When Codex calls responsesapi, we currently send `session_id`,
    `thread_id`, and `turn_id` among other things as
    `client_metadata["x-codex-turn-metadata"]`. This PR adds
    `forked_from_thread_id` which helps explain the "lineage" of a forked
    thread.
    
    ## What's changed
    
    - Track the immediate history source copied into a forked thread through
    thread/session creation, including subagent and review turn metadata
    paths.
    - Include `forked_from_thread_id` in Codex turn metadata while
    preventing turn-scoped Responses API client metadata from overwriting
    Codex-owned lineage fields.
    - Add coverage for fork lineage in turn metadata and the app-server
    Responses API request path.
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • test: clean up apply_patch allow-session artifact (#24611)
    ## Why
    
    The
    `approving_apply_patch_for_session_skips_future_prompts_for_same_file`
    integration test writes `apply_patch_allow_session.txt` under the
    process cwd while exercising outside-workspace patch approval behavior.
    With `just test` now being the normal validation path, that file can be
    left behind in the checkout when the test runs or fails, creating
    confusing untracked state.
    
    ## What changed
    
    - Registers the resolved `apply_patch_allow_session.txt` path with
    `tempfile::TempPath` before the test removes and recreates it through
    `apply_patch`.
    - Preserves the existing outside-workspace path shape so the approval
    behavior under test does not change.
    - Lets `TempPath` remove the generated file when the test exits,
    including panic paths.
    
    ## Verification
    
    - `just test -p codex-core --test all
    approving_apply_patch_for_session_skips_future_prompts_for_same_file`
  • Move MCP tool naming mode into manager (#21576)
    ## Why
    
    The `non_prefixed_mcp_tool_names` feature should be applied where MCP
    tools become model-visible, not by remapping names later in core.
    Keeping the decision in `McpConnectionManager` construction makes
    `ToolInfo` the single shaped view that spec building, deferred tool
    search, routing, and unavailable-tool placeholders can consume directly.
    
    This also preserves the existing external behavior while the feature is
    off, and keeps the feature-on behavior for code mode and hooks explicit
    at the manager boundary.
    
    ## What Changed
    
    - Add `McpToolNameMode` to `codex-mcp` and flow it through `McpConfig`
    into `McpConnectionManager::new`.
    - Normalize MCP `ToolInfo` names in the manager using either
    legacy-prefixed namespaces or non-prefixed namespaces; the legacy path
    adds `mcp__` without restoring the old trailing namespace suffix.
    - Remove the core-side MCP name remapping path so specs, tool search,
    session resolution, and unavailable-tool placeholder construction use
    the manager-provided `ToolName` values directly.
    - Keep code mode flattening on the `__` namespace separator.
    - Preserve hook compatibility by giving non-prefixed MCP hook names
    legacy `mcp__...` matcher aliases.
    - Add/adjust integration and unit coverage for non-prefixed code-mode
    behavior, hook matching with the feature on and off, and manager-level
    legacy prefixing.
    
    ## Testing
    
    - `cargo test -p codex-mcp --lib`
    - `cargo test -p codex-core --lib tools::spec::tests -- --nocapture`
    - `cargo test -p codex-core --lib mcp_tools -- --nocapture`
    - `cargo test -p codex-core --lib mcp_tool_exposure -- --nocapture`
    - `cargo test -p codex-core --test all mcp_tool -- --nocapture`
    - `cargo test -p codex-core --test all search_tool -- --nocapture`
    - `cargo test -p codex-core --test all hooks_mcp -- --nocapture`
    - `cargo test -p codex-core --test all
    code_mode_uses_non_prefixed_mcp_tool_names_when_feature_enabled --
    --nocapture`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-features`
  • fix: drop flake (#24588)
    Dropping already commented out stuff
  • chore: stop consuming legacy config profiles (#24076)
    ## Why
    
    The old config-profile mechanism should no longer influence runtime
    behavior now that profile selection has moved to file-based `--profile`
    config files. Core already rejects a selected legacy `profile = "..."`
    with a migration error in
    [`core/src/config/mod.rs`](https://github.com/openai/codex/blob/d6451fcb79edc4a71bc9e811bcda06fd3c36562e/codex-rs/core/src/config/mod.rs#L2521-L2529),
    but a few residual consumers still read legacy `[profiles.*]` data while
    performing managed-feature checks and personality migration.
    
    That kept dead legacy profile state relevant after selection had been
    removed, and could make personality migration depend on a stale or
    missing old profile.
    
    ## What changed
    
    - Stop scanning legacy `[profiles.*]` feature settings when validating
    managed feature requirements.
    - Make personality migration consider only top-level `personality` and
    `model_provider` settings.
    - Remove the now-unused `ConfigToml::get_config_profile` helper.
    - Update personality migration coverage to verify that legacy profile
    personality fields and missing legacy profile names no longer affect
    that migration path.
    
    This keeps the legacy `profile` / `profiles` config shape available for
    the remaining compatibility and migration diagnostics; it only removes
    these behavior consumers.
    
    ## Verification
    
    - Updated `core/tests/suite/personality_migration.rs` for the new
    legacy-profile behavior.
    - Focused test command: `cargo test -p codex-core
    personality_migration`.
  • code-mode: merge stored values by key (#24159)
    ## Summary
    
    Change code-mode stored value updates to merge writes by key instead of
    replacing the session's complete stored-value map after each cell
    completes.
    
    Previously, each cell received a snapshot of stored values and returned
    the complete resulting map. When multiple cells ran concurrently, a
    later completion could overwrite values written by another cell because
    it committed an older snapshot.
    
    This change moves stored-value ownership into `CodeModeService`:
    
    - Each runtime starts from the service's current stored values.
    - Runtime completion reports only keys written by that cell.
    - The service merges those writes into the current stored-value map on
    successful completion.
    - Core no longer replaces its stored-value state from a cell result.
    
    As a result, concurrently executing cells can update different stored
    keys without clobbering one another.
    
    The move into CodeModeService is motivated by a desire to have this
    lifetime tied to a new lifetime object on that side in a subsequent PR.
  • Default function tools into tool hooks (#23757)
    # Why
    
    `PreToolUse`, `PostToolUse`, and `updatedInput` coverage for local
    function tools currently depends on each handler remembering to wire up
    the hook contract itself. That makes coverage easy to miss as new
    function tools are added, even though most of them share the same basic
    shape: a model-facing function call with JSON arguments.
    
    # What
    
    This makes `CoreToolRuntime` provide the default hook contract for
    ordinary local function tools:
    
    - build generic `PreToolUse` and `PostToolUse` payloads from the
    function tool name and arguments
    - apply `updatedInput` rewrites back into function-tool arguments
    through the same default path
    - let tool outputs override the post-hook input or response when they
    have a more stable hook-facing contract
    
    The exceptions stay explicit:
    
    - hosted tools remain outside the generic local function path
    - code-mode `wait` and `write_stdin` opt out for now
    - `PostToolUse` feedback replaces only the model-visible response, so
    code mode keeps its typed tool result
    
    With the generic path in place, the MCP and extension-tool adapters no
    longer need their own duplicate pre/post hook plumbing. The new coverage
    exercises the registry default plus end-to-end local function behavior
    for pre-hook blocking, `updatedInput` rewriting, and post-hook context.
  • Add trace_id to TurnStartedEvent (#23980)
    ## Why
    [Recent PR](https://github.com/openai/codex/pull/22709) removed
    `trace_id` from `TurnContextItem`.
    
    ## What changed
    - Add to `TurnStartedEvent` so rollout consumers can correlate turns
    with telemetry traces.
    - Note that the branch name is out of date because I originally re-added
    to `TurnContextItem`, but we decided to move it to `TurnStartedEvent`.
    
    ## Verification
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-core --lib
    regular_turn_emits_turn_started_without_waiting_for_startup_prewarm`
    - `cargo test -p codex-core --test all
    emits_warning_when_resumed_model_differs`
    - `cargo test -p codex-rollout`
    - `cargo test -p codex-state`
  • retry remote compaction v2 requests (#23951)
    ## Why
    
    Remote compaction v2 sends a normal `/responses` request with a
    compaction trigger. It should follow the retry semantics used by normal
    Responses streaming calls for transient stream/request failures, while
    keeping a smaller per-transport retry budget because compact attempts
    can run much longer than normal turns.
    
    ## What changed
    
    - Add a v2 compaction retry loop that uses `stream_max_retries`,
    matching normal Responses turn retry mechanics.
    - Cap the compact v2 retry budget at 2 retries per transport with
    `min(stream_max_retries, 2)`.
    - Retry retryable request-open and post-open stream collection failures
    through the same loop.
    - Use the existing 200ms exponential backoff and requested retry delay
    handling used by normal turn retries.
    - Emit the same `Reconnecting... n/max` stream-error notification
    pattern.
    - Fall back from WebSockets to HTTPS after the compact v2 stream retry
    budget is exhausted, then reset the retry counter for HTTPS.
    - Keep final remote-compaction failure logging after retries/fallback
    are exhausted.
    - Treat compact stream EOF before `response.completed` as a retryable
    stream failure.
    - Add compact v2 regression coverage with `request_max_retries = 0` and
    `stream_max_retries = 2`, covering both request-open failure and
    opened-stream EOF in one end-to-end test.
    
    ## Tests
    
    - `just fmt`
    - `cargo test -p codex-core remote_compact_v2`
    - `just fix -p codex-core`
  • Prefer just test over cargo test in docs (#23910)
    `cargo test` for the core and other crates fails on a fresh macOS
    checkout without the right stack size variable. This change encourages
    using the just test command that sets the environment up correctly.
    
    As a bonus, this should encourage agents to get more benefit out of
    nextest's parallel execution.
  • Allow parallel MCP tool calls when annotated readOnly (#23750)
    ## Summary
    - Treat MCP tools with `readOnlyHint: true` as parallel-safe even when
    `supports_parallel_tool_calls` is unset or `false`.
    - Keep server-level `supports_parallel_tool_calls` as an additive
    override for non-read-only tools.
    - Add focused unit coverage for the MCP handler eligibility decision.
    - Update RMCP integration coverage to keep the serial baseline on a
    mutable tool, verify read-only concurrency without server opt-in, and
    preserve the server opt-in concurrency path separately.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-core --lib tools::handlers::mcp::tests::`
    - `cargo test -p codex-core --test all
    stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in`
    - `cargo test -p codex-core --test all
    stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently`
    - `cargo test -p codex-rmcp-client`
  • Add subagent identity to hook inputs (#22882)
    # What
    
    When a normal hook fires inside a thread-spawned subagent, Codex now
    includes these optional top-level fields in the hook input:
    
    - `agent_id`: the child thread id
    - `agent_type`: the subagent role
    
    Root-agent hook inputs omit these fields. `SubagentStart` and
    `SubagentStop` keep their existing required `agent_id` and `agent_type`
    fields because those events are inherently subagent-scoped.
    
    This does not change matcher behavior. Tool hooks still match on tool
    name, compact hooks still match on trigger, and `UserPromptSubmit` still
    ignores matchers. Only `SubagentStart` and `SubagentStop` match on
    `agent_type`.
  • Remove plugin hooks feature flag (#22552)
    # Why
    
    This is a follow-up stacked on top of the `plugin_hooks` default-on
    change. Once we are comfortable making plugin hooks part of the normal
    plugin behavior, the separate feature flag stops buying us much and
    leaves extra branching/cache state behind.
    
    # What
    
    - remove the `PluginHooks` feature and generated config-schema entries
    - make plugin hook loading/listing follow plugin enablement directly
    - drop plugin-manager cache/state that only existed to distinguish
    hook-flag toggles
    - remove tests and fixtures that modeled `plugin_hooks = true/false`
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`
  • cli: rename profile v2 flag to --profile (#23883)
    ## Why
    
    Profile v2 is taking over the user-facing profile selection path, so the
    CLI no longer needs to expose the transitional `--profile-v2` spelling.
    This switches the public args surface to `--profile` before the
    remaining legacy profile plumbing is removed separately.
    
    ## What
    
    - Rebind `--profile` and `-p` to the v2 profile name argument that
    selects `$CODEX_HOME/<name>.config.toml`.
    - Stop parsing the legacy shared CLI profile argument while keeping its
    implementation path in place for follow-up cleanup.
    - Update CLI validation, profile-name parse errors, and the
    legacy-profile collision message/tests to refer to `--profile`.
    
    ## Testing
    
    - `cargo test -p codex-cli -p codex-config -p codex-protocol -p
    codex-utils-cli`
  • [codex] Stabilize subagent start hook test (#23882)
    ## What
    
    Remove the exact captured request-count assertion from the
    `SubagentStart` hook integration test while still waiting for the child
    request that matches the injected hook context.
    
    ## Why
    
    The test owns the start-hook behavior and already verifies that the
    child request reaches the context matcher plus that the start/session
    hook logs have the expected invocations. Counting every request captured
    by the response mock makes the test sensitive to lifecycle timing
    outside that contract and has been flaky in CI.
    
    ## Testing
    
    - `cargo test -p codex-core --test all
    suite::subagent_notifications::subagent_start_replaces_session_start_and_injects_context
    -- --exact`
  • Trace logical websocket request after untraced warmup (#23581)
    ## Why
    
    `prewarm_websocket` intentionally stays out of rollout inference
    tracing, but the next traced websocket request can still reuse the
    warmup `response_id` and send an empty `input` delta. If tracing records
    that wire payload verbatim, replay sees an incremental request whose
    parent was never traced and cannot reconstruct the conversation.
    
    This fixes that at the producer boundary instead of relaxing
    `rollout-trace` replay semantics around unresolved
    `previous_response_id` values.
    
    ## What
    
    - track whether the last websocket response came from an untraced warmup
    and clear that state when the websocket session is reset or reconnected
    - when a traced websocket request reuses that warmup parent, keep
    sending the compressed websocket request on the wire but record the
    logical `ResponsesApiRequest` in the rollout trace
    - add a regression test that proves replay reconstructs the logical user
    message even though the websocket follow-up carries
    `previous_response_id = warm-1` with empty `input`
    - update `InferenceTraceAttempt::record_started` docs to reflect that
    callers may record a logical request rather than the exact transport
    payload
    
    ## Testing
    
    - `cargo test -p codex-core --test all
    responses_websocket_request_prewarm_traces_logical_request`
  • [codex] Add plugin id to MCP tool call items (#23737)
    Add owning plugin id to MCP tool call items so we can better filter them
    at plugin level.
    
    ## Summary
    - add optional `plugin_id` to MCP tool-call items and legacy begin/end
    events
    - propagate plugin metadata into emitted core items and app-server v2
    `ThreadItem::McpToolCall`
    - preserve plugin ids through app-server replay/redaction paths and
    regenerate v2 schema fixtures
    
    ## Testing
    - `just write-app-server-schema`
    - `just fmt`
    - `just fix -p codex-core`
    - `cargo test -p codex-protocol -p codex-app-server-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core mcp_tool_call_item_includes_plugin_id --lib`
    - `cargo check -p codex-tui --tests`
    - `cargo check -p codex-app-server --tests`
    - `git diff --check`
    
    ## Notes
    - `just fix -p codex-core` completed with two non-fatal
    `too_many_arguments` warnings on the touched MCP notification helpers.
    - A broader `cargo test -p codex-core` run passed core unit tests, then
    hit shell/sandbox/snapshot failures in the integration target.
    - A broader app-server downstream run hit the existing
    `in_process::tests::in_process_start_clamps_zero_channel_capacity` stack
    overflow; `cargo test -p codex-exec` also hit the existing sandbox
    expectation mismatch in
    `thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile`.
  • Honor client-resolved service tier defaults (#23537)
    ## Why
    
    Model catalog responses can now advertise a nullable
    `default_service_tier` for each model. Codex needs to preserve three
    distinct states all the way from config/app-server inputs to inference:
    
    - no explicit service tier, so the client may apply the current model
    catalog default when FastMode is enabled
    - explicit `default`, meaning the user intentionally wants standard
    routing
    - explicit catalog tier ids such as `priority`, `flex`, or future tiers
    
    Keeping those states distinct prevents the UI from showing one tier
    while core sends another, especially after model switches or app-server
    `thread/start` / `turn/start` updates.
    
    ## What Changed
    
    - Plumbed `default_service_tier` through model catalog protocol types,
    app-server model responses, generated schemas, model cache fixtures, and
    provider/model-manager conversions.
    - Added the request-only `default` service tier sentinel and normalized
    legacy config spelling so `fast` in `config.toml` still materializes as
    the runtime/request id `priority`.
    - Moved catalog default resolution to the TUI/client side, including
    recomputing the effective service tier when model/FastMode-dependent
    surfaces change.
    - Updated app-server thread lifecycle config construction so
    `serviceTier: null` preserves explicit standard-routing intent by
    mapping to `default` instead of internal `None`.
    - Kept core responsible for validating explicit tiers against the
    current model and stripping `default` before `/v1/responses`, without
    applying catalog defaults itself.
    
    ## Validation
    
    - `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
    - `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
    - `cargo test -p codex-tui service_tier`
    - `cargo test -p codex-protocol service_tier_for_request`
    - `cargo test -p codex-core get_service_tier`
    - `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
    service_tier`
  • Make goals feature on by default and no longer experimental (#23732)
    ## Why
    
    The `goals` feature is ready to be available without requiring users to
    opt into experimental features. Keeping it behind the beta flag leaves
    persisted thread goals and automatic goal continuation disabled by
    default.
    
    This PR also marks the goal-related app server APIs and events as no
    longer experimental.
    
    ## What changed
    
    - Mark `goals` as `Stage::Stable`.
    - Enable `goals` by default in `codex-rs/features/src/lib.rs`.
  • Add SubagentStop hook (#22873)
    # What
    
    <img width="1792" height="1024" alt="image"
    src="https://github.com/user-attachments/assets/8f81d232-5813-4994-a61d-e42a05a93a3e"
    />
    
    `SubagentStop` runs when a thread-spawned subagent turn is about to
    finish. Thread-spawned subagents use `SubagentStop` instead of the
    normal root-agent `Stop` hook.
    
    Configured handlers match on `agent_type`. Hook input includes the
    normal stop fields plus:
    
    - `agent_id`: the child thread id.
    - `agent_type`: the resolved subagent type.
    - `agent_transcript_path`: the child subagent transcript path.
    - `transcript_path`: the parent thread transcript path.
    - `last_assistant_message`: the final assistant message from the child
    turn, when available.
    - `stop_hook_active`: `true` when the child is already continuing
    because an earlier stop-like hook blocked completion.
    
    `SubagentStop` shares the same completion-control semantics as `Stop`,
    scoped to the child turn:
    
    - No decision allows the child turn to finish.
    - `decision: "block"` with a non-empty `reason` records that reason as
    hook feedback and continues the child with that prompt.
    - `continue: false` stops the child turn. If `stopReason` is present,
    Codex surfaces it as the stop reason.
    
    # Lifecycle Scope
    
    Only thread-spawned subagents run `SubagentStop`.
    
    Internal/system subagents such as Review, Compact, MemoryConsolidation,
    and Other do not run normal `Stop` hooks and do not run `SubagentStop`.
    This avoids exposing synthetic matcher labels for internal
    implementation paths.
    
    # Stack
    
    1. #22782: add `SubagentStart`.
    2. This PR: add `SubagentStop`.
    3. #22882: add subagent identity to normal hook inputs.
  • windows-sandbox: feed setup from resolved permissions (#23167)
    ## Why
    
    This is the next step in the Windows sandbox migration away from the
    legacy `SandboxPolicy` abstraction. #22923 moved write-root and token
    decisions onto `ResolvedWindowsSandboxPermissions`, but setup and
    identity still accepted `SandboxPolicy` and converted internally. This
    PR pushes that conversion outward so the setup path consumes the
    resolved Windows permission view directly.
    
    ## What Changed
    
    - Changed `SandboxSetupRequest` to carry
    `ResolvedWindowsSandboxPermissions` instead of `SandboxPolicy` plus
    policy cwd.
    - Updated setup refresh/elevation and identity credential preparation to
    use resolved permissions for read roots, write roots, network identity,
    and deny-write payload planning.
    - Removed the production `allow.rs` legacy wrapper; allow-path
    computation now takes resolved permissions directly.
    - Added a permissions-based world-writable audit entry point while
    keeping the existing legacy wrapper for compatibility.
    - Updated legacy ACL setup and the core Windows setup bridge to
    construct resolved permissions at the boundary.
    - Hardened the Windows sandbox integration test helper staging so Bazel
    retries can reuse an already-staged helper if a prior sandbox helper
    process still has the executable open.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core --test all --no-run`
    - `just fix -p codex-windows-sandbox`
    - `just fix -p codex-core`
    - Attempted `cargo check -p codex-windows-sandbox --target
    x86_64-pc-windows-gnullvm`, but the local machine is missing
    `x86_64-w64-mingw32-clang`; Windows CI should cover that target.
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23167).
    * #23715
    * #23714
    * __->__ #23167
  • windows-sandbox: drive write roots from resolved permissions (#22923)
    ## Why
    
    This is the third PR in the Windows sandbox `SandboxPolicy` ->
    `PermissionProfile` migration stack.
    
    #22896 introduced `ResolvedWindowsSandboxPermissions`, and #22918 moved
    elevated runner IPC to carry `PermissionProfile`. This PR starts moving
    the remaining setup/spawn helpers away from asking legacy enum questions
    like “is this `WorkspaceWrite`?” and toward resolved runtime permission
    questions like “does this profile require write capability roots?”
    
    ## What changed
    
    - Added resolved-permissions helpers for network identity and
    write-capability detection.
    - Moved setup write-root gathering to operate on
    `ResolvedWindowsSandboxPermissions`, with the legacy `SandboxPolicy`
    wrapper left in place for existing call sites.
    - Updated identity setup, elevated capture setup, and world-writable
    audit denies to use resolved write roots.
    - Updated spawn preparation to carry resolved permissions in
    `SpawnContext` and use them for network blocking, setup write roots,
    elevated capability SID selection, and legacy capability roots.
    - Removed a now-unused legacy write-root helper.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `just fix -p codex-windows-sandbox`
    - Existing stack checks are green on #22896 and #22918; CI has started
    for this PR.
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22923).
    * #23715
    * #23714
    * #23167
    * __->__ #22923
  • Support compact SessionStart hooks (#21272)
    # Why
    
    Compaction replaces the live conversation history, so hooks that use
    `SessionStart` to re-inject durable model context need a way to run
    again after that rewrite.
    
    Related - #19905 adds dedicated compact lifecycle hooks
    
    # What
    
    - add `compact` as a supported `SessionStart` source and matcher value
    - change pending `SessionStart` state from a single slot to a small FIFO
    queue so `resume` / `startup` / `clear` can be preserved alongside a
    later `compact`
    - drain all queued `SessionStart` sources before the next model request,
    preserving their original order
    
    # Testing
    
    The new integration coverage verifies both the basic `compact` matcher
    path and the stacked `resume` -> `compact` case where both hooks
    contribute `additionalContext` to the next model turn.
  • Migrate exec-server remote registration to environments (#23633)
    ## Summary
    - migrate exec-server remote registration naming from executor to
    environment
    - align CLI, public Rust exports, registry error messages, and relay
    test fixtures with the environment registry contract
    - keep the live registration path and response model consistent with
    `/cloud/environment/{environment_id}/register`
    
    ## Verification
    - `cargo test -p codex-exec-server
    remote::tests::register_environment_posts_with_auth_provider_headers
    --manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
    - `cargo test -p codex-exec-server --test relay
    multiplexed_remote_environment_routes_independent_virtual_streams
    --manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
    - `cargo check -p codex-cli --manifest-path
    /Users/richardlee/code/codex/codex-rs/Cargo.toml` (still running when PR
    opened; will update after completion if needed)
  • [codex] Preserve raw code-mode exec output by default (#23564)
    ## Why
    Code mode can use nested unified exec calls as data sources. When those
    calls omit `max_output_tokens`, code mode should receive raw command
    output so the script can parse or summarize it itself. When code mode
    does provide `max_output_tokens`, that explicit nested budget should be
    respected, including values above the default unified exec limit, rather
    than being capped before code mode sees the result.
    
    ## What
    - Preserve direct unified exec truncation behavior, while letting
    code-mode exec/write_stdin keep `max_output_tokens` as `None` unless
    explicitly supplied.
    - Make code-mode tool results use raw output when no explicit limit is
    present, and use the explicit nested limit directly when one is
    specified.
    - Refactor unified exec output formatting so `truncated_output` takes
    the caller-selected token budget.
    - Add e2e integration coverage for explicit nested exec limits, omitted
    nested exec limits, outer exec limit propagation, omitted-limit outputs
    that exceed both the default and a small truncation policy, explicit
    nested limits above those caps, and high explicit limits that still
    compact larger command output.
    - Reuse the code-mode turn setup helper while directly asserting the
    exact exec output item in each test.
    
    ## Testing
    - `just fmt`
    - `git diff --check`
    - Not run locally per repo guidance; CI should validate the e2e
    integration tests.
  • Fix stale background terminal poll events (#23231)
    ## Why
    
    Issue #23214 reports `/ps` showing no background terminals while the
    status line still says it is waiting for a background terminal. The race
    is in core: `write_stdin` can poll a process that exits before the
    response returns. The process manager correctly returns `process_id:
    None`, but the handler still emitted a `TerminalInteraction` event using
    the requested session id, causing clients to believe a dead process was
    still being polled.
    
    Fixes #23214.
    
    ## What changed
    
    - Suppress `TerminalInteraction` events for empty `write_stdin` polls
    once `response.process_id` is `None`.
    - Continue emitting interactions for non-empty stdin, even if that input
    causes the process to exit before the response returns.
    - Extend the unified exec integration test to assert completed empty
    polls do not emit terminal interactions.
    
    ## Verification
    
    - `cargo test -p codex-core --test all
    unified_exec_emits_one_begin_and_one_end_event`
    - `cargo test -p codex-core --test all
    unified_exec_emits_terminal_interaction_for_write_stdin`
    
    `cargo test -p codex-core` currently aborts in unrelated
    `agent::control::tests::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
    with a reproducible stack overflow.
  • Split plugin install discovery into list and request tools (#23372)
    ## Summary
    - Add `list_available_plugins_to_install` as the inventory step for
    plugin and connector install suggestions.
    - Slim `request_plugin_install` so it only handles the actual
    elicitation, instead of carrying the full discoverable list in its
    prompt.
    - Emit send-time telemetry when an install elicitation is dispatched,
    including requested tool identity in the event payload.
    - Emit install-result telemetry through `SessionTelemetry`, including
    tool type, user response action, and completion status.
    - Update registration and tests to cover the new two-step flow while
    keeping the existing `tool_suggest` feature gate unchanged.
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-core request_plugin_install`
    - `cargo test -p codex-core list_available_plugins_to_install`
    - `cargo test -p codex-core
    install_suggestion_tools_can_be_registered_without_search_tool`
    - `cargo test -p codex-otel
    manager_records_plugin_install_suggestion_metric`
    - `cargo test -p codex-otel
    manager_records_plugin_install_elicitation_sent_metric`
    - `just fix -p codex-core`
    - `just fix -p codex-tools`
    - `just fix -p codex-otel`
    - `cargo check -p codex-core`
  • Make local environment optional in EnvironmentManager (#23369)
    ## Summary
    - make `EnvironmentManager` local environment/runtime paths optional
    - simplify constructor surface around snapshot materialization
    - rename local env accessors to `require_local_environment` /
    `try_local_environment`
    
    ## Validation
    - devbox Bazel build for touched crate surfaces
    - `//codex-rs/exec-server:exec-server-unit-tests`
    - `//codex-rs/app-server-client:app-server-client-unit-tests`
    - filtered touched `//codex-rs/core:core-unit-tests` cases
  • Add SubagentStart hook (#22782)
    # What
    
    `SubagentStart` runs once when Codex creates a thread-spawned subagent,
    before that child sends its first model request. Thread-spawned
    subagents use `SubagentStart` instead of the normal root-agent
    `SessionStart` hook.
    
    Configured handlers match on the subagent `agent_type`, using the same
    value passed to `spawn_agent`. When no agent type is specified, Codex
    uses the default agent type.
    
    Hook input includes the normal session-start fields plus:
    
    - `agent_id`: the child thread id.
    - `agent_type`: the resolved subagent type.
    
    `SubagentStart` may return `hookSpecificOutput.additionalContext`. That
    context is added to the child conversation before the first model
    request.
    
    # Lifecycle Scope
    
    Only thread-spawned subagents run `SubagentStart`.
    
    Internal/system subagents such as Review, Compact, MemoryConsolidation,
    and Other do not run normal `SessionStart` hooks and do not run
    `SubagentStart`. This avoids exposing synthetic matcher labels for
    internal implementation paths.
    
    Also the `SessionStart` hook no longer fires for subagents, this matches
    behavior with other coding agents' implementation
    
    # Stack
    
    1. This PR: add `SubagentStart`.
    2. #22873: add `SubagentStop`.
    3. #22882: add subagent identity to normal hook inputs.
  • Make deny canonical for filesystem permission entries (#23493)
    ## Why
    Filesystem permission profiles used `none` for deny-read entries, which
    is less direct than the action the entry actually represents. This
    change makes `deny` the canonical filesystem permission spelling while
    preserving compatibility for older configs that still send `none`.
    
    ## What changed
    - rename `FileSystemAccessMode::None` to `Deny`
    - serialize and generate schemas with `deny` as the canonical value
    - retain `none` only as a legacy input alias for temporary config
    compatibility
    - update filesystem glob diagnostics and regression coverage to use the
    canonical spelling
    - refresh config and app-server schema fixtures to match the new wire
    shape
    
    ## Validation
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core config_toml_deserializes_permission_profiles
    --lib`
    - `cargo test -p codex-core
    read_write_glob_patterns_still_reject_non_subpath_globs --lib`
    
    Earlier in the session, a broad `cargo test -p codex-core` run reached
    unrelated pre-existing failures in timing/snapshot/git-info tests under
    this environment; the targeted surfaces touched by this PR passed
    cleanly.
  • chore: namespace v1 sub-agent tools (#23475)
    ## Why
    
    The v1 sub-agent tools are a single tool family, but they were exposed
    as separate flat function tools. This makes the model-visible surface
    less clearly grouped and leaves the legacy names in the same flat
    namespace as newer agent tooling.
    
    ## What
    
    - Wraps the v1 `spawn_agent`, `send_input`, `resume_agent`,
    `wait_agent`, and `close_agent` specs in the `multi_agent_v1` namespace.
    - Registers the corresponding handlers with namespaced runtime tool
    names.
    - Updates tool-planning, deferred tool search, and sub-agent
    notification tests to assert the namespace shape and child `spawn_agent`
    lookup.
    
    ## Verification
    
    - Updated `codex-core` coverage for the v1 multi-agent tool plan,
    deferred tool search output, and sub-agent tool descriptions.
  • Defer v1 multi-agent tools behind tool search (#23144)
    Summary: defer v1 multi-agent tools when tool_search and namespace tools
    are available; keep concise searchable descriptions and move the v1
    usage guidance into developer instructions; add targeted coverage.
    Testing: not run per request; ran just fmt.
  • Add body_after_prefix auto-compact token limit scope (#22870)
    ## Why
    
    `model_auto_compact_token_limit` has only been able to budget the full
    active context. That makes it hard to set a small "growth since
    compaction" budget for sessions that preserve a large carried window
    prefix: the preserved prefix can consume the whole budget and force
    immediate repeated compaction.
    
    This PR adds an opt-in `body_after_prefix` scope so callers can apply
    `model_auto_compact_token_limit` to sampled output and later growth
    after the current carried prefix, while still forcing compaction before
    the full model context window is exhausted.
    
    ## What changed
    
    - Adds `AutoCompactTokenLimitScope` with the existing `total` behavior
    as the default and a new `body_after_prefix` mode:
    [`config_types.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/protocol/src/config_types.rs#L24-L37).
    - Threads `model_auto_compact_token_limit_scope` through config loading,
    `Config`, `core-api`, and app-server v2 schema/TypeScript generation.
    - Records the first observed input-token count for a `body_after_prefix`
    compaction window and uses it as the baseline when deciding whether the
    scoped auto-compaction budget is exhausted:
    [`turn.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/core/src/session/turn.rs#L743-L781).
    - Keeps a hard context-window cap in `body_after_prefix`, so scoped
    budgeting cannot let the active context overrun the usable window.
    
    ## Verification
    
    Added compact-suite coverage for the two key behaviors:
    `body_after_prefix` does not re-compact just because the carried prefix
    is larger than the scoped budget, and it still compacts when the total
    active context reaches the configured context window:
    [`compact.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/core/tests/suite/compact.rs#L3003-L3128).