Commit Graph

3059 Commits

  • fix(config): use deny for Unix socket permissions (#24970)
    ## Why
    
    Unix socket permissions still accepted and displayed `"none"` while file
    permissions use the clearer `"deny"` spelling. This keeps network Unix
    socket policy vocabulary consistent with filesystem policy vocabulary.
    
    ## What changed
    
    - Replace the Unix socket permission variant and serialized spelling
    from `none` to `deny` across config, feature configuration, and network
    proxy types.
    - Update app-server v2 serialization, TUI debug output, focused tests,
    and generated schemas to expose `"deny"`.
    - Add coverage for denied Unix socket entries in managed requirements
    and profile overlay behavior.
    
    ## Security
    
    This is a vocabulary change for explicit Unix socket rejection, not a
    network access expansion. Denied entries continue to be omitted from the
    effective allowlist.
    
    ## Validation
    
    - `just fmt`
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just test -p codex-config -p codex-core -p codex-app-server-protocol
    -p codex-tui -E
    'test(network_requirements_are_preserved_as_constraints_with_source) |
    test(network_permission_containers_project_allowed_and_denied_entries) |
    test(network_toml_overlays_unix_socket_permissions_by_path) |
    test(permissions_profiles_resolve_extends_parent_first_with_child_overrides)
    | test(network_requirements_serializes_canonical_and_legacy_fields) |
    test(debug_config_output_formats_unix_socket_permissions)'`\n- Automatic
    `bench-smoke` follow-up from `just test`\n- `cargo clippy -p
    codex-config -p codex-core -p codex-features -p codex-network-proxy -p
    codex-app-server-protocol -p codex-app-server -p codex-tui --all-targets
    -- -D warnings`
  • 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`
  • fix: cancel Windows sandbox on network denial (#19880)
    ## Why
    
    When Guardian or the sandbox network proxy detects and denies a network
    attempt, core cancels the associated execution through `ExecExpiration`.
    The Windows sandbox capture path was only forwarding the timeout
    component of that expiration state. As a result, a sandboxed Windows
    command whose network attempt had already been denied could keep running
    until its timeout elapsed rather than terminating promptly in response
    to the denial.
    
    This change closes that cancellation-propagation gap for Windows sandbox
    execution.
    
    ## What changed
    
    - Added `WindowsSandboxCancellationToken` as the cancellation hook
    exposed to Windows capture backends.
    - Extracted the cancellation token from `ExecExpiration` in core and
    passed it to both the direct and elevated Windows sandbox capture paths
    alongside the existing timeout.
    - Updated direct capture to poll for either process exit, timeout, or
    cancellation and to terminate cancelled processes without reporting them
    as timed out.
    - Updated elevated capture to watch for cancellation and send the
    existing `Terminate` IPC frame to the elevated runner. The watcher parks
    for 50 ms between checks to bound response latency without a tight busy
    wait.
    - Added Windows regression coverage for a long-running PowerShell
    command: cancellation ends capture before its timeout and does not set
    `timed_out`.
    - Added a visible skip diagnostic when that PowerShell-dependent
    regression test cannot execute, and consolidated the duplicated
    expiration-policy branch identified in review.
    
    ## Security
    
    This improves enforcement after a denied network attempt has been
    attributed to a Windows sandboxed execution: the command no longer
    remains alive simply because Windows capture lost the cancellation
    signal.
    
    This PR does not claim to make Windows offline mode an airtight
    no-network or no-exfiltration boundary. It does not introduce
    AppContainer or change how network denial is detected; it makes an
    already-detected denial promptly stop the affected sandboxed command.
    
    ## Validation
    
    ### Commands run
    
    - `just fmt`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core network_denial`
    - `cargo clippy -p codex-core -p codex-windows-sandbox --tests --no-deps
    -- -D warnings`
    - `just argument-comment-lint -p codex-windows-sandbox -p codex-core`
    
    The new capture regression is `cfg(target_os = "windows")`, so Windows
    CI is the execution coverage for that test path. The local macOS test
    runs validate the host-runnable crate and core network-denial behavior.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • 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.
  • Add feature-gated standalone image generation extension (#24723)
    ## Why
    
    Add a standalone image generation path that can be exercised
    independently of hosted Responses image generation, while retaining the
    hosted tool as fallback unless the extension is actually available to
    the model.
    
    ## What changed
    
    - Added the `codex-image-generation-extension` crate with standalone
    generate/edit execution, prior-image selection for edits, model-visible
    image output, and local generated-image persistence.
    - Installed the extension in app-server behind the disabled-by-default
    `imagegenext` feature and backend eligibility checks.
    - Updated core tool planning so eligible `image_gen.imagegen` exposure
    replaces hosted `image_generation`, while unavailable configurations
    retain hosted fallback.
    - Added coverage for extension behavior, edit history reuse, feature
    gating, auth eligibility, and hosted-tool replacement.
    - The extension is installed through app-server only in this PR; other
    execution paths retain hosted image generation because hosted
    replacement occurs only when the standalone executor is actually
    registered and model-visible.
    - The initial extension contract intentionally fixes the image model to
    `gpt-image-2` and uses automatic image parameters.
    - Native generated-image history/card parity and rollout persistence
    cleanup are intentionally deferred follow-up work.
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - `just test -p codex-features`
    - `just test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates`
    - `just test -p codex-app-server`
    - `just fix -p codex-image-generation-extension -p codex-features -p
    codex-core -p codex-app-server`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Wire task completion into thread-idle lifecycle (#24928)
    ## Why
    
    #24744 introduced the thread idle lifecycle hook so idle continuation
    can be owned by lifecycle contributors instead of hard-coded goal
    runtime plumbing. Task completion still called
    `goal_runtime_apply(GoalRuntimeEvent::MaybeContinueIfIdle)` directly, so
    the post-turn idle transition remained goal-specific and did not notify
    generic thread lifecycle contributors.
    
    ## What Changed
    
    - Add `Session::emit_thread_idle_lifecycle_if_idle()` to gate idle
    emission on both no active turn and no queued trigger-turn mailbox work.
    - Call that helper when a task clears the active turn, replacing the
    direct `GoalRuntimeEvent::MaybeContinueIfIdle` path.
    - Cover the behavior with `codex-core` session tests for emitting after
    task completion and suppressing idle emission while trigger-turn mailbox
    work is pending.
    
    ## Verification
    
    - New tests in `core/src/session/tests.rs` exercise the idle lifecycle
    emission and trigger-turn mailbox guard.
  • Fix extension turn item emitter test event ordering (#24936)
    ## Why
    
    PR #24813 added extension `TurnItemEmitter` coverage and introduced a
    test that records a conversation history item before asserting
    extension-emitted turn item events.
    
    `record_conversation_items()` also emits a `RawResponseItem` event to
    observers. The test was reading from the same event receiver and
    expected the next event to be `ItemStarted`, so the test failed reliably
    once the setup history item was present.
    
    ## What Changed
    
    Update
    `passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call` to
    consume and assert the expected setup `RawResponseItem` before checking
    the extension `ItemStarted`, `WebSearchBegin`, `ItemCompleted`, and
    `WebSearchEnd` events.
    
    This is test-only and does not change extension runtime behavior.
    
    ## Verification
    
    - `cargo nextest run --no-fail-fast -p codex-core
    tools::handlers::extension_tools::tests::passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call`
  • Reap stale multi-agent slots (#24903)
    ## Summary
    
    - Let `close_agent` clean up an agent that is still registered in
    `AgentRegistry` even when its underlying thread is already missing.
    - Preserve the explicit-close boundary: for known stale thread-spawn
    agents, mark the persisted spawn edge `Closed`, then treat
    `ThreadNotFound` / `InternalAgentDied` as a successful close so the
    registry slot can be released.
    - Add a regression for MultiAgentV2 task-name targets where
    `close_agent("worker")` succeeds after the worker thread has already
    disappeared.
    
    ## Motivation
    
    A worker can disappear from `ThreadManager` while its metadata still
    exists in the root `AgentRegistry`. Before this change, the close tool
    failed while trying to subscribe to the missing thread status, so it
    never reached the cleanup path that releases the registered agent slot.
    With `agents.max_threads = 1`, an explicit close of that stale task-name
    agent could fail and leave the session unable to spawn a replacement.
    
    ## Scope
    
    This PR intentionally does not add automatic stale-agent reaping to
    `spawn_agent`, `resume_agent`, or `list_agents`. A thread being missing
    from `ThreadManager` is not the same as an explicit close: persisted
    open spawn edges are still the durable source of truth for resume and
    task-name ownership until `close_agent` is called.
    
    ## Validation
    
    - `just test -p codex-core -E
    'test(multi_agent_v2_close_agent_reaps_stale_task_name_target) |
    test(resume_agent_from_rollout_reopens_open_descendants_after_manager_shutdown)'`
    - `just fix -p codex-core`
  • extension-api: add TurnItemEmitter to tool calls (#24813)
    ## Why
    Extension-contributed tools need to emit visible turn items through
    Codex's normal event and persistence pipeline.
    
    ## What
    - Add `TurnItemEmitter` to extension `ToolCall`s and route the core
    implementation through `Session::emit_turn_item_*`.
    - Hold weak session and turn references so retained tool calls cannot
    keep host state alive.
    - Provide a no-op emitter for extension test callers.
    
    ## Test Plan
    - `just test -p codex-core -E
    'test(passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call)'`
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Add turn error lifecycle contributor (#24916)
    Summary
    - Add TurnErrorInput and TurnLifecycleContributor::on_turn_error to the
    extension API.
    - Emit the turn-error lifecycle from core turn error paths, including
    usage limit failures.
    - Add direct lifecycle coverage for the emitted error facts and stores.
    
    Tests
    - just fmt
    - git diff --check
    - Not run: full tests or clippy (per instructions)
  • Add thread start contributor facts (#24915)
    Summary: add session source and persistent-state availability to
    ThreadStartInput; populate them from session init; update existing goal
    test harness constructors. Tests: just fmt; git diff --check. No full
    tests or clippy run per request.
  • Add Guardian review metrics (#24897)
    ## Why
    
    Guardian reviews already emit analytics events, but we do not expose
    aggregate OpenTelemetry metrics for review volume, latency, token usage,
    or terminal outcomes. That makes it harder to monitor Guardian behavior
    during rollouts and to compare review outcomes by source, action type,
    session kind, model, and failure mode.
    
    ## What Changed
    
    - Added Guardian review metric names for count, total duration, time to
    first token, and token usage in `codex-rs/otel`.
    - Added `core/src/guardian/metrics.rs` to convert
    `GuardianReviewAnalyticsResult` into sanitized metric tags covering
    decision, terminal status, failure reason, approval request source,
    reviewed action, session kind, risk/outcome, model, reasoning effort,
    and context/truncation state.
    - Emitted the new metrics from `track_guardian_review` for each terminal
    Guardian review result.
    
    ## Testing
    
    - Added
    `guardian_review_metrics_record_counts_durations_and_token_usage`, which
    verifies the emitted count, duration, TTFT, token usage histograms, and
    tag set through the in-memory metrics exporter.
  • [codex] Fix Guardian argument comment lint (#24902)
    ## Summary
    - Add the required `/*parent_thread_id*/` argument comment at the
    Guardian review session test callsite flagged by CI.
    
    ## Validation
    - `just fmt`
    - Not run: clippy/tests, per request; CI will cover them.
  • Thread Guardian cache key through session (#24895)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/session/session.rs. Validation was not run per
    request; this branch is expected to rely on the companion split PRs.
  • Assert Guardian prompt cache key reuse (#24894)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/tests.rs. Validation was not run per request;
    this branch is expected to rely on the companion split PRs.
  • Add Guardian review prompt cache key (#24893)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/review_session.rs. Validation was not run per
    request; this branch is expected to rely on the companion split PRs.
  • Export Guardian prompt cache key helper (#24892)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/mod.rs. Validation was not run per request;
    this branch is expected to rely on the companion split PRs.
  • Stabilize Guardian client cache key handling (#24891)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/client.rs. Validation was not run per request; this
    branch is expected to rely on the companion split PRs.
  • Move memories root setup out of core config (#24758)
    ## Why
    
    Config loading should not create or write-authorize the memories root
    just because memory support exists. Memory startup is the code path that
    actually materializes that tree.
    
    ## What
    
    - Stop creating the memories root during Config load and remove it from
    legacy workspace-write projections.
    - Grant the memories root read access only when the memories feature and
    use_memories are enabled.
    - Create the memories root inside memories startup before seeding
    extension instructions.
    - Update config and startup tests around the ownership boundary.
    
    ## Tests
    
    - just fmt
    - just fix -p codex-core
    - just fix -p codex-memories-write
    - just test -p codex-core
    memory_tool_makes_memories_root_readable_without_creating_or_widening_writes
    workspace_write_includes_configured_writable_root_once_without_memories_root
    permission_profile_override_keeps_memories_root_out_of_legacy_projection
    permissions_profiles_allow_direct_write_roots_outside_workspace_root
    default_permissions_profile_populates_runtime_sandbox_policy
    - just test -p codex-memories-write memories_startup_creates_memory_root
    
    Note: a broader just test -p codex-core run is not clean in this
    sandbox; it hit missing test_stdio_server plus seatbelt, realtime, and
    environment-sensitive failures. The changed config tests above pass.
  • [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`
  • Update rmcp to 1.7.0 (#24763)
    WIll make it easier to uprev when the new draft spec is supported.
    
    Also updates reqwest where needed for compatibility but doesn't update
    it everywhere since this is already a large diff.
    
    The new version of rmcp handles certain kinds of authentication failures
    differently, this patch includes support for identifying the failing scope
    in a WWW-Authenticate header.
  • fix(linux-sandbox): preserve shell cleanup on interruption (#22729)
    ## Why
    Interrupted `shell_command` calls can race with the outer tool-dispatch
    cancellation path. When that happens, the runtime future may be dropped
    before the spawned process gets a chance to run `SIGTERM` cleanup. For
    bwrapd-backed Linux sandbox commands, that can leave synthetic
    protected-path mount bookkeeping such as `.git/.codex` registrations
    under `/tmp` behind after a TUI interruption.
    
    The relevant cancellation points are the outer dispatch race in
    [`core/src/tools/parallel.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/parallel.rs#L91-L132)
    and the process shutdown logic in
    [`core/src/exec.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/exec.rs#L1367-L1393).
    
    ## What changed
    - Keep `shell_command` dispatch alive long enough for the runtime to
    finish cancellation cleanup instead of immediately returning the
    synthetic aborted response.
    - Fold shell-turn cancellation into the existing `ExecExpiration` path
    in
    [`core/src/tools/runtimes/shell.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/runtimes/shell.rs#L267-L274),
    so cancellation and timeout behavior stay centralized.
    - On cancellation, send `SIGTERM` first, wait briefly for cleanup to
    run, then hard-kill any remaining descendants in the original process
    group.
    - Treat `ESRCH` as an already-gone process-group cleanup case in
    `codex-utils-pty`, which keeps best-effort teardown from surfacing a
    stale-process race as an error.
    
    ## Verification
    - `cargo test -p codex-core cancellation`
    - Added regression coverage for:
      - `shell_tool_cancellation_waits_for_runtime_cleanup`
      - `process_exec_tool_call_cancellation_allows_sigterm_cleanup`
  • chore: enable namespace tools for Bedrock (#24713)
    Client-side namespace tools are now supported by bedrock. Enable
    `namespace_tools` for the Amazon Bedrock provider while continuing to
    disable unsupported hosted tools such as image generation and web
    search.
  • [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)
  • Drop startup context when truncating forked rollouts (#24751)
    ## Summary
    - Change last-`n` fork truncation to start at the first fork-turn
    boundary instead of returning the full rollout when the fork history is
    shorter than the requested window.
    - Add coverage for the startup-prefix case in both rollout truncation
    tests and agent control spawn behavior.
    - Ensure bounded forked children still rebuild context after the cached
    prefix is truncated.
    
    ## Testing
    - Added unit coverage for truncation behavior when the parent history is
    under the requested fork-turn limit.
    - Added an agent control test covering bounded fork spawn behavior with
    startup context present.
    - Not run (not requested).
  • 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.
  • fix(core): instrument stalled tool-listing handoff (#24667)
    ## Why
    
    When a turn needs a follow-up request after tool output is recorded,
    Codex can still appear stuck in `Thinking` before the next `/responses`
    request is opened. The existing local trace showed the last completed
    response and the absence of a new backend request, but it did not show
    whether the stall was in tool-router preparation or later request setup.
    
    Issue: N/A (internal incident investigation)
    
    ## What Changed
    
    Added trace spans around the pre-stream tool-router handoff in
    `core/src/session/turn.rs`, including the `built_tools` phase and the
    MCP manager read lock.
    
    Added per-server MCP tool-listing spans and trace breadcrumbs in
    `codex-mcp/src/connection_manager.rs` with startup snapshot /
    startup-complete state so a pending MCP client is visible in feedback
    logs instead of looking like a silent hang.
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-mcp`
    - `just test -p codex-core` (prior full rerun fails in this workspace on
    unrelated integration tests: code-mode output length expectations, one
    shell timeout formatting assertion, and shell snapshot timeouts; latest
    review-fix rerun compiled and passed 1160 tests before I stopped the
    abnormally slow unrelated suite)
  • [codex] Remove obsolete goal continuation turn marker (#24658)
    ## Why
    
    `continuation_turn_id` was introduced to distinguish synthetic goal
    continuation turns for the no-tool continuation suppression heuristic.
    #20523 removed that heuristic, but left the marker behind. It is still
    written and cleared without affecting any runtime decision.
    
    ## What Changed
    
    - Remove `GoalRuntimeState::continuation_turn_id`.
    - Remove the marker setter/clearer and their now-no-op start, finish,
    and abort call sites.
    
    ## Testing
    
    - Not run yet (deferred at request).
  • [codex-analytics] add grouped session id to runtime events (#24655)
    ## Why
    - Runtime analytics events report `thread_id`, which identifies the
    individual thread emitting an event
    - They don't report `session_id`, which identifies the shared session
    for a root thread and its subagent threads
    - Emitting both identifiers allows analytics to group related activity
    
    ## What Changed
    - Adds `session_id` to relevant analytics events (thread_initalized,
    turn, turn_steer, compaction, guardian_review)
    - Tracks each thread's session ID in the analytics reducer so subsequent
    thread scoped events emit the same value
    - Carries the shared session ID through subagent initialization
    
    ## Verification
    - `just test -p codex-analytics` validates event payloads and subagent
    session grouping.
    - Focused `codex-app-server` tests validate session IDs for thread,
    turn, and steer events.
    - Focused `codex-core` tests validate root and subagent session ID
    propagation.
  • Restore legacy image detail values (#24644)
    ## Why
    
    Older persisted rollouts can contain `input_image.detail` values of
    `auto` or `low` from before `ImageDetail` was narrowed to
    `high`/`original`. Current deserialization rejects those values, which
    can make resume skip later compacted checkpoints and reconstruct an
    oversized raw suffix before the next compaction attempt.
    
    Confirmed Sentry reports fixed by this compatibility path:
    
    - [CODEX-1H3F](https://openai.sentry.io/issues/7500642496/)
    - [CODEX-1H6N](https://openai.sentry.io/issues/7501025347/)
    - [CODEX-1JDP](https://openai.sentry.io/issues/7504549065/)
    - [CODEX-1HW6](https://openai.sentry.io/issues/7503407986/)
    
    ## Background
    
    [openai/codex#20693](https://github.com/openai/codex/pull/20693) added
    image-detail plumbing for app-server `UserInput` so input images could
    explicitly request `detail: original`. The Slack discussion behind that
    PR was about ScreenSpot / bridge evals where user input images were
    resized, while tool output images already had MCP/code-mode ways to
    request image detail.
    
    In review, the intended new API surface was narrowed to `high` and
    `original`: default to `high`, allow `original` when callers need
    unchanged image handling, and avoid encouraging new `auto` or `low`
    usage. That policy still makes sense for newly emitted values.
    
    The missing compatibility piece is persisted history. Older rollouts can
    already contain `auto` and `low`, and resume reconstructs typed history
    by deserializing those rollout records. Rejecting old values at that
    boundary causes valid compacted checkpoints to be skipped. This PR
    restores `auto` and `low` as real variants so old records deserialize
    and round-trip without being rewritten as `high`, while product paths
    can continue to default to `high` and avoid emitting `auto` for new
    behavior.
    
    ## What changed
    
    - Restored `ImageDetail::Auto` and `ImageDetail::Low` as first-class
    protocol values.
    - Preserved `auto`/`low` through rollout deserialization, MCP image
    metadata, code-mode image output, and schema/type generation.
    - Kept local image byte handling conservative: only `original` switches
    to original-resolution loading; `auto`/`low`/`high` continue through the
    resize-to-fit path while retaining their detail value.
    - Added regression coverage for enum round-tripping and code-mode `low`
    detail handling.
    
    ## Testing
    
    - `just write-app-server-schema`
    - `just test -p codex-protocol`
    - `just test -p codex-tools`
    - `just test -p codex-code-mode`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core
    suite::rmcp_client::stdio_image_responses_preserve_original_detail_metadata`
    - `just test -p codex-core
    suite::code_mode::code_mode_can_use_mcp_image_result_with_image_helper`
    - Loaded broken rollouts on local fixed builds, and started/completed
    new turns.
    
    I also attempted `just test -p codex-core`; the local broad run did not
    finish green: 2559 tests run, 2467 passed, 55 flaky, 91 failed, 1 timed
    out. The failures were broad timeout/deadline failures across unrelated
    areas; targeted changed-path core tests above passed.
  • windows-sandbox: remove SandboxPolicy runner plumbing (#23813)
    ## Why
    
    The Windows sandbox runner still carried the old `SandboxPolicy`
    compatibility path even though core now computes `PermissionProfile`.
    That meant Windows command-runner execution could only see the legacy
    projection, so profile-only filesystem rules such as deny globs were not
    part of the runner input.
    
    ## What Changed
    
    - Removed the Windows-local `SandboxPolicy` parser/export and deleted
    `windows-sandbox-rs/src/policy.rs`.
    - Changed restricted-token capture/session setup, elevated setup,
    world-writable audit, read-root grant, and command-runner session APIs
    to accept `PermissionProfile` plus the profile cwd.
    - Bumped the elevated command-runner IPC protocol to version 2 because
    `SpawnRequest` now carries `permission_profile` /
    `permission_profile_cwd` instead of the legacy `policy_json_or_preset` /
    `sandbox_policy_cwd` fields.
    - Updated core exec, unified exec, debug-sandbox, TUI setup/grant flows,
    and app-server setup to pass the actual effective `PermissionProfile`.
    - Left regression coverage asserting the old IPC policy fields are
    absent and the runner serializes tagged `PermissionProfile` JSON.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-app-server
    request_processors::windows_sandbox_processor`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-app-server
    -p codex-cli -p codex-tui`
    - `just fix -p codex-cli -p codex-tui`
    - `just fix -p codex-windows-sandbox -p codex-tui`
    - `rg "\\bSandboxPolicy\\b" codex-rs/windows-sandbox-rs` returned no
    matches.
    
    Note: `cargo test -p codex-cli` was attempted but did not reach crate
    tests because local disk filled while compiling dependencies (`No space
    left on device`). The targeted clippy pass compiled the affected CLI/TUI
    surfaces afterward.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23813).
    * #24108
    * __->__ #23813
  • 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.
  • standalone websearch extension (#23823)
    ## Summary
    
    Add the extension-backed standalone `web.run` tool so Codex can call the
    standalone search endpoint through the `codex-api` search client and
    return its encrypted output to Responses.
    
    - gate the new tool behind `standalone_web_search`
    - install the extension in the app-server thread registry and hide
    hosted `web_search` when standalone search is enabled for OpenAI
    providers so the two paths stay mutually exclusive
    - build search context from persisted history using a small tail
    heuristic: previous user message, assistant text between the last two
    user turns capped at about 1k tokens, and current user message
    
    ## Test Plan
    
    - `cargo test -p codex-web-search-extension`
    - `cargo test -p codex-api`
    - `cargo test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates`
  • Move memory state to a dedicated SQLite DB (#24591)
    ## Summary
    
    Generated memory rows and their stage-one/stage-two job state currently
    live in `state_5.sqlite` alongside thread metadata. That makes memory
    cleanup and regeneration share the main state schema even though those
    rows are memory-pipeline data and can be rebuilt independently from the
    durable thread records.
    
    This PR moves the memory-owned tables into a dedicated
    `memories_1.sqlite` runtime database while keeping thread metadata in
    `state_5.sqlite`.
    
    ## Changes
    
    - Adds a separate memories DB runtime, migrator, path helpers, telemetry
    kind, and Bazel compile data for `state/memory_migrations`.
    - Introduces `MemoryStore` behind `StateRuntime::memories()` and moves
    memory table/job operations onto that store.
    - Drops the old memory tables from the state DB and recreates their
    schema in `state/memory_migrations/0001_memories.sql`.
    - Updates memory startup, citation usage tracking, rollout pollution
    handling, `debug clear-memories`, and app-server `memory/reset` to
    operate through the memories DB.
    - Preserves cross-DB behavior by hydrating thread metadata from the
    state DB when selecting visible memory outputs and checking stage-one
    staleness.
    
    ## Verification
    
    - Added/updated `codex-state` tests for deleted-thread memory visibility
    and already-polluted phase-two enqueue behavior.
    - Updated `debug clear-memories`, app-server `memory/reset`, and
    memories startup tests to seed and assert memory rows through
    `memories_1.sqlite`.
  • feat: add manual and remote_v2 tags to compaction metric (#24608)
    ## Why
    `codex.task.compact` only distinguished `local` vs `remote`, which made
    it hard to answer simple counter questions in Statsig. Manual `/compact`
    and automatic compaction were collapsed together, and the legacy remote
    path was also collapsed with `remote_compaction_v2`.
    
    ## What Changed
    - route `codex.task.compact` through a shared helper in
    `core/src/tasks/mod.rs`
    - add a `manual=true|false` tag so manual and automatic compaction can
    be counted separately
    - split the remote tag into `remote` and `remote_v2`
    - emit the metric from the inline auto-compaction path in
    `core/src/session/turn.rs` as well as the manual `CompactTask` path in
    `core/src/tasks/compact.rs`
    - add focused unit coverage for the new tag shapes in
    `core/src/tasks/mod_tests.rs`
    
    ## Verification
    - added unit coverage in `core/src/tasks/mod_tests.rs` covering manual
    `remote_v2` tags and automatic `local` tags
  • tui: add named permission profile picker (#21559)
    ## Why
    
    Users who opt into named permission profiles through
    `default_permissions` or `[permissions.*]` should stay in named-profile
    semantics when they open `/permissions`. The legacy picker rewrites
    those users into anonymous preset state, which loses the active profile
    identity and hides custom configured profiles.
    
    ## What changed
    
    - Switch `/permissions` to a profile-aware picker when profile mode is
    active.
    - Show friendly built-in labels instead of raw `:` profile syntax.
    - Include configured custom profiles and their descriptions in the
    picker.
    - Route selections through the split TUI profile-selection flow below
    this PR.
    - Add TUI snapshots and regression coverage for built-ins, custom
    profiles, and conflicting legacy runtime overrides.
    
    ## Stack
    
    1. [#22931](https://github.com/openai/codex/pull/22931):
    runtime/session/network propagation for active permission profiles.
    2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
    plumbing and guardrail flow.
    3. **This PR**: profile-aware `/permissions` menu and custom profile
    display.
    
    ## UX impact
    
    In profile mode, `/permissions` shows the same human-facing built-ins
    users already know:
    
    ```text
    Default
    Auto-review
    Full Access
    Read Only
    locked-down
    web-enabled
    ```
    
    Selecting `locked-down` keeps `active_permission_profile =
    Some("locked-down")`; selecting a built-in keeps the friendly label
    while switching to its named built-in profile.
    
    ## Screenshots
    
    Live `$test-tui` smoke screenshots uploaded through GitHub attachments:
    
    **Profile mode with built-ins and custom profiles**
    
    <img width="832" alt="Profile mode permissions picker with custom
    profiles"
    src="https://github.com/user-attachments/assets/58b72431-418c-4839-9e39-575076db4c8f"
    />
    
    **Legacy mode remains anonymous preset picker**
    
    <img width="1232" alt="Legacy permissions picker"
    src="https://github.com/user-attachments/assets/95f413ab-4cee-411c-9afb-92580a885c97"
    />
    
    <img width="1296" height="906" alt="image"
    src="https://github.com/user-attachments/assets/ea381a78-9904-4aa2-828f-b7f2e43f60f2"
    />
    
    <img width="705" height="207" alt="Screenshot 2026-05-18 at 2 58 00 PM"
    src="https://github.com/user-attachments/assets/2fa6dd71-0296-449e-a6de-a72d78a1cb70"
    />
    
    ## Validation
    
    - `git diff --cached --check` before commit.
    - Full test run skipped at the user request while pushing the split
    stack.
  • feat: gate dedicated memories tools in config (#24600)
    ## Why
    
    The memories extension already has dedicated `list`, `read`, `search`,
    and `add_ad_hoc_note` tools, but app-server registration was still
    disabled. The memories app collaborator needs an explicit config switch
    so those native extension tools can be exposed intentionally, without
    making ordinary memory prompt usage automatically register the dedicated
    tool surface.
    
    ## What changed
    
    - Added `[memories].dedicated_tools`, defaulting to `false`, to
    `MemoriesToml` / `MemoriesConfig`.
    - Regenerated `core/config.schema.json` for the new setting.
    - Registered the memories extension as a `ToolContributor`, while
    keeping tool contribution gated on both memories being enabled and
    `dedicated_tools = true`.
    - Added tests for the disabled default, the enabled dedicated-tools
    path, and installer registration.
    
    ## Verification
    
    - `just test -p codex-config -p codex-memories-extension`
  • 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`
  • [codex] Make active turn task singular (#24105)
    ## Why
    
    `ActiveTurn` already runs at most one task: starting a task requires
    that no task is present, and replacement aborts existing work first.
    Representing that state as an `IndexMap` leaves a multi-task shape for a
    single-task invariant and makes each lifecycle lookup operate like a
    collection lookup.
    
    The slot remains optional because goal continuation uses an empty active
    turn as a reservation while deciding whether to start continuation work.
    
    ## What changed
    
    - Replace `ActiveTurn.tasks` with `task: Option<RunningTask>`.
    - Update task abort/completion, session lookup and steering, input-queue
    matching, goal reservation, and network-approval lookup to operate on
    the singular slot.
    - Mutate the singular task slot directly instead of retaining
    collection-era add/remove/take helpers.
    - Record token usage on the completing active task span without a
    regular-task-only opt-in flag.
    
    ## Validation
    
    - `cargo test -p codex-core --lib session::tests::steer_input`
    - `cargo test -p codex-core --lib
    session::tests::abort_empty_active_turn_preserves_pending_input`
    - `cargo test -p codex-core --lib
    session::tests::queued_response_items_for_next_turn_move_into_next_active_turn`
    - `cargo test -p codex-core --lib
    session::tests::active_goal_continuation_runs_again_after_no_tool_turn`
    - `cargo test -p codex-core --lib
    session::tests::abort_regular_task_emits_turn_aborted_only`
    - `cargo test -p codex-core --lib session::input_queue::tests`
  • centralize Responses retry policy (#24131)
    ## Why
    
    #23951 added remote compaction v2 retries, but it left the retry and WS
    -> HTTPS fallback behavior duplicated between normal Responses turns and
    compaction. This follow-up centralizes the common retry handling so
    future changes to fallback, retry delay, retry notifications, and retry
    sleep do not have to be kept in sync across both callsites.
    
    ## What changed
    
    - Added `core/src/responses_retry.rs` with a shared handler for
    retryable Responses stream errors.
    - Reused that handler from normal turn sampling and remote compaction
    v2.
    - Kept each callsite responsible for its retry budget: normal turns
    still use `stream_max_retries`, while compaction v2 still uses
    `min(stream_max_retries, 2)`.
    - Preserved caller-specific behavior around non-retryable errors,
    context-window errors, usage-limit errors, and compact-specific final
    failure logging.
    
    The shared handler now owns:
    
    - WS -> HTTPS fallback warning emission
    - retry delay selection, including server-requested stream retry delay
    - retry logging
    - first-WebSocket-retry notification suppression
    - `Reconnecting... n/max` stream-error notification
    - sleeping before the next retry attempt
    
    ## Verification
    
    - `cargo test -p codex-core remote_compact_v2`
    - `cargo test -p codex-core websocket_fallback`
    - `just fix -p codex-core`
    
    Did not run the full workspace test suite.
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • 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.