Commit Graph

905 Commits

  • store and expose parent_thread_id on Threads (#25113)
    ## Why
    
    This PR
    https://github.com/openai/codex/pull/24161#discussion_r3325692763
    revealed a subagent data modeling issue, where we overloaded
    `forked_from_id` to also mean `parent_thread_id`. That's incorrect since
    guardian and review subagents can be a subagent and NOT fork the main
    thread's history.
    
    The solution here is to explicitly store a new `parent_thread_id` on
    `SessionMeta`, alongside `forked_from_id` which already exists. While
    we're at it, also expose it in the app-server protocol on the `Thread`
    object.
    
    A thread->subagent relationship and a fork of thread history are
    orthogonal concepts.
    
    ## What Changed
    
    - Added top-level `parent_thread_id` persistence on `SessionMeta` and
    runtime/session plumbing through `SessionConfiguredEvent`,
    `CodexSpawnArgs`, `SessionConfiguration`, `ThreadConfigSnapshot`,
    `TurnContext`, and `ModelClient`.
    - Made turn metadata, request headers, analytics, and subagent-start
    events read the separate runtime/top-level parent field instead of
    deriving general parent lineage from `SessionSource` or
    `forked_from_thread_id`.
    - Passed parent lineage separately at delegated subagent, review,
    guardian, agent-job, and multi-agent spawn construction sites;
    copied-history fork lineage remains derived only from `InitialHistory`.
    - Persisted and exposed parent lineage through rollout/thread-store
    projections and app-server v2 `Thread.parentThreadId`.
    - Updated app-server README text and regenerated app-server schema
    fixtures for the additive `parentThreadId` response field.
  • Add cloud-managed config layer support (#24620)
    ## Summary
    
    PR 3 of 5 in the cloud-managed config client stack.
    
    Adds enterprise-managed cloud config as a first-class config layer
    source. The layer metadata is preserved through config loading,
    diagnostics, debug output, hook attribution, and app-server protocol
    surfaces.
    
    ## Details
    
    - Enterprise-managed config becomes a normal config layer source with
    backend-supplied `id` and display `name` attached for provenance.
    - These layers are designed to behave like non-file managed config: they
    can surface syntax/type diagnostics by layer name even though there is
    no physical config file.
    - Relative path settings are resolved from a stored config base so
    cloud-delivered config remains consistent with existing MDM-delivered
    config semantics.
    - Hook attribution distinguishes config-delivered hooks from
    requirements-delivered hooks via `HookSource::CloudManagedConfig`.
    - This remains pull-based and snapshot-oriented; the PR adds layer
    identity/diagnostics, not dynamic reload behavior.
    
    ## Validation
    
    Validated through the targeted stack checks after rebasing onto current
    `main`:
    
    - Rust crate tests for
    config/hooks/cloud-config/backend-client/app-server-protocol
    - Filtered `codex-core` and `codex-app-server` `cloud_config_bundle`
    tests
    - Python generated-file contract test
    - `cargo shear --deny-warnings`
    - Targeted `argument-comment-lint` for config/hooks
  • [codex] Avoid forced directory refresh during plugin install auth checks (#25381)
    ## Summary
    - Use normal directory loading for plugin install app metadata so
    install avoids forced directory refresh while still loading metadata on
    cold cache.
    - Continue force-refreshing codex_apps tools for auth state.
    - Add regression coverage that pre-warms the directory cache and asserts
    install returns cached app metadata without extra directory requests.
    
    ## Validation
    - just fmt
    - git diff --check
    - just test -p codex-app-server plugin_install_returns_apps_needing_auth
    plugin_install_filters_disallowed_apps_needing_auth (blocked locally:
    cargo-nextest is not installed)
  • Add thread archive CLI commands (#25021)
    ## Problem
    
    Saved threads can already be archived through app-server RPCs, but the
    command line did not expose direct archive or unarchive commands.
    
    ## Solution
    
    Add `codex archive <thread>` and `codex unarchive <thread>`, resolving
    UUIDs or exact thread names before calling the existing `thread/archive`
    and `thread/unarchive` RPCs. The commands support scoped remote flags so
    callers can target remote app-server endpoints when archiving or
    unarchiving threads.
    
    This also fixes a long-standing bug in `codex resume <thread id>` and
    `codex fork <thread id>` that I found when testing the new commands.
    These operations shouldn't be allowed on archived sessions. They now
    fail with an error that tells the user to run `codex unarchive <thread
    id>` first.
    
    ## Verification
    
    Added app-server coverage for rejecting archived thread resume by id and
    checking that the error includes the matching `codex unarchive <thread
    id>` command.
  • Constrain Windows sandbox requirements (#23766)
    # Why
    
    Managed requirements can already constrain sandbox policy choices, but
    Windows sandbox implementation selection was still resolved
    independently from those requirements. That left the TUI able to
    continue through the unelevated fallback even when an organization wants
    to require the elevated Windows sandbox implementation.
    
    # What
    
    - Add `[windows].allowed_sandbox_implementations` requirements support
    for the Windows `elevated` and `unelevated` implementations.
    - Apply that allowlist during core config resolution so disallowed
    configured or feature-selected Windows sandbox implementations fall back
    to an allowed implementation with the existing requirements warning
    path.
    - Reuse the existing TUI Windows setup prompts to block disallowed
    unelevated continuation, keep required elevated setup in front of the
    user, and refuse to persist a TUI-selected Windows sandbox mode that
    requirements disallow.
    
    # Semantics
    
    | Allowed | Selected | Effective |
    | --- | --- | --- |
    | `["elevated"]` | `unelevated` / unset | `elevated` |
    | `["unelevated"]` | `elevated` / unset | `unelevated` |
    | `["elevated", "unelevated"]` | `elevated` | `elevated` |
    | `["elevated", "unelevated"]` | `unelevated` | `unelevated` |
    | `["elevated", "unelevated"]` | unset | `elevated` |
    
    Availability is handled by interactive setup surfaces after allowlist
    resolution. If the effective elevated implementation is not ready,
    elevated-only requirements block on setup. When unelevated is also
    allowed, the UI may offer the existing unelevated fallback.
    
    ## TUI Screens
    
    If elevated setup is not already complete:
    ```
      Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control
      network access.
      Learn more <https://developers.openai.com/codex/windows>
    
    › 1. Set up default sandbox (requires Administrator permissions)
      2. Quit
    ```
    
    If admin setup fails under `["elevated"]`:
    ```
      Couldn't set up your sandbox with Administrator permissions
    
      Your organization requires the default sandbox before Codex can continue.
      Learn more <https://developers.openai.com/codex/windows>
    
    › 1. Try setting up admin sandbox again
      2. Quit
    ```
    
    # Next Steps
    
    
    - extend the requirements/readout surface, such as
    `configRequirements/read`, so clients can inspect the loaded
    `[windows].allowed_sandbox_implementations` requirement instead of
    inferring it from Windows setup state
    - consider extending `windowsSandbox/readiness` as well
    - update the App startup guide, setup flow, and banner surfaces so an
    elevated-only requirement omits any continue-unelevated escape hatch and
    blocks startup until a permitted implementation is ready;
    - preserve the existing unelevated fallback path when requirements allow
    it, including the `["unelevated"]` case where elevated is disallowed
  • thread-store: store permission profiles (#23165)
    ## Why
    
    `SandboxPolicy` is the legacy compatibility shape, but
    `codex-thread-store` still exposed it through `StoredThread`,
    `ThreadMetadataPatch`, and live metadata sync. That kept thread-store
    consumers tied to the legacy representation and meant richer permission
    profile data could not round-trip through thread metadata or cold
    rollout reconciliation.
    
    ## What Changed
    
    - Replaced thread-store `sandbox_policy` API fields with canonical
    `PermissionProfile` fields.
    - Persist new permission-profile metadata as canonical JSON in the
    existing SQLite metadata slot while continuing to read older legacy
    sandbox policy values.
    - Updated local, in-memory, live metadata sync, and rollout extraction
    paths to propagate `TurnContextItem::permission_profile()`.
    - Re-materialize legacy permission metadata against the final rollout
    cwd when rollout-derived metadata replaces stale SQLite summaries.
    - Updated affected app-server and core test constructors to build
    `PermissionProfile` values directly.
    
    ## Test Plan
    
    - `cargo test -p codex-state`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-app-server
    summary_from_stored_thread_preserves_millisecond_precision --lib`
    - `cargo test -p codex-core realtime_context --lib`
  • Fix fs/watch debounce batching (#24716)
    ## Summary
    
    `fs/watch` was using a local debounce wrapper whose deadline was
    initialized once and then reused after the first batch. Once that stale
    deadline was in the past, later file changes could bypass the intended
    200ms debounce and send noisier `fs/changed` notifications.
    
    This moves the debounce wrapper into `codex-file-watcher` as
    `DebouncedWatchReceiver`, resets the debounce deadline for each event
    batch, preserves pending paths across cancelled receives, and updates
    app-server `fs/watch` to use the shared wrapper.
    
    Fixes #24692.
  • Add runtime extra skill roots API (#24977)
    ## Summary
    - Add v2 `skills/extraRoots/set` to replace app-server process-local
    standalone skill roots. The setting is not persisted, accepts missing
    roots, and `extraRoots: []` clears the runtime set.
    - Wire runtime roots into core skill discovery for `skills/list` and
    turn loads, clear skill caches on set, and register the roots with the
    skills watcher so later filesystem changes emit `skills/changed`.
    - Update app-server docs, generated JSON/TypeScript schemas, and
    coverage for serialization, missing roots, empty clears, and restart
    behavior.
    
    ## Testing
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core-skills`
    - `cargo test -p codex-app-server
    skills_extra_roots_set_updates_process_runtime_roots`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core-skills`
    - `just fix -p codex-app-server`
  • 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.
  • [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`
  • 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>
  • Expose MCP server info as part of server status (#24698)
    # Summary
    
    Expose MCP server info via App Server (when available) so apps can
    render a richer MCP experience
  • feat(app-server): include turns page on thread resume (#23534)
    ## Summary
    
    The client currently calls `thread/resume` to establish live updates and
    immediately follows it with `thread/turns/list` to hydrate recent turns.
    This lets `thread/resume` return that page directly, eliminating a round
    trip and the ordering/deduplication gap between the two calls.
    
    Experimental clients opt in with `initialTurnsPage: { limit,
    sortDirection, itemsView }`. The response returns `initialTurnsPage` as
    a `TurnsPage`, including cursors for paging further back in history.
    Keeping the controls in a nested opt-in object provides the useful
    `thread/turns/list` knobs without spreading page-specific parameters
    across `thread/resume`.
    
    ## Verification
    
    - `just fmt`
    - `just write-app-server-schema --experimental`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_resume_initial_turns_page_matches_requested_turns_list_page
    --tests`
    - `cargo test -p codex-app-server
    thread_resume_rejoins_running_thread_even_with_override_mismatch
    --tests`
    - `just fix -p codex-app-server-protocol -p codex-app-server`
  • Allow runtime enablement for remote plugins (#24707)
    experimentalFeature/enablement/set now accepts remote_plugin as a
    supported runtime feature key
  • 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.
  • Attach Windows sandbox log to feedback reports (#24623)
    ## Why
    
    Windows sandbox diagnostics are currently hard to recover from
    `/feedback` even though they are often the most useful artifact when
    debugging sandbox behavior. Now that sandbox logging uses daily rolling
    files, feedback can safely include the current day's sandbox log without
    uploading the old ever-growing legacy `sandbox.log`.
    
    ## What changed
    
    - Add a `codex-windows-sandbox` helper that resolves the current daily
    sandbox log from `codex_home`.
    - When feedback is submitted with logs enabled on Windows, app-server
    attaches today's sandbox log if it exists.
    - Upload the attachment under the stable filename `windows-sandbox.log`,
    independent of the dated on-disk filename.
    - Keep existing raw `extra_log_files` behavior unchanged for rollout and
    desktop log attachments.
    
    ## Verification
    
    - `cargo fmt -p codex-app-server -p codex-windows-sandbox`
    - `cargo test -p codex-windows-sandbox
    current_log_file_path_for_codex_home_uses_sandbox_dir`
    - `cargo test -p codex-app-server
    windows_sandbox_log_attachment_uses_current_log`
    - Manual CLI/TUI `/feedback` test confirmed Sentry received
    `windows-sandbox.log`.
  • 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
  • Respect resume cwd overrides for idle cached threads (#24528)
    Fixes #24186.
    
    ## Why
    When the TUI resumes a thread through the local app-server daemon with a
    selected workspace, `thread/resume` can hit an already-loaded but idle
    cached thread. That path previously rejoined the cached `CodexThread`,
    so cwd/config overrides in `ThreadResumeParams` were ignored and the
    resumed session kept using the old cwd.
    
    ## What changed
    App-server now treats a loaded-but-idle thread with no subscribers as a
    cache entry when resume overrides differ: it unloads that cached thread
    and lets the normal resume path rebuild it with the requested
    cwd/config. Threads that still have subscribers, or active runtime work,
    continue to rejoin the existing loaded thread so in-flight state remains
    observable.
    
    The existing thread teardown helper was generalized from
    archive-specific cleanup to shared unload cleanup for this 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`.
  • Use thread config for TUI MCP inventory (#24532)
    ## Summary
    `/mcp` in the TUI should reflect the current loaded thread, including
    project-local MCP servers from that thread config. Before this change,
    `mcpServerStatus/list` only read the latest global MCP config, so the
    active chat could miss project-local servers.
    
    This adds optional `threadId` to `mcpServerStatus/list`. When present,
    app-server resolves the loaded thread and lists MCP status from the
    refreshed effective config for that thread; when omitted, existing
    global config behavior stays unchanged.
    
    The TUI now sends the active chat thread id for `/mcp` and `/mcp
    verbose`, carries that origin through the async inventory result, and
    ignores stale completions if the user has switched threads before the
    fetch returns. The app-server schemas were regenerated.
    
    ## Follow-up
    Once this app-server API change lands, the desktop app should make the
    same `threadId` plumbing so its MCP inventory also uses the current
    thread config.
    
    Fixes #23874
  • Wire app-server extension event sink (#24586)
    ## Why
    
    The goal extension already emits `ThreadGoalUpdated` events, but
    production app-server thread extensions were built with the default
    no-op extension event sink. That meant extension-driven goal updates
    could be produced without ever reaching app-server clients.
    
    ## What changed
    
    - Build app-server thread extensions with a host-provided
    `ExtensionEventSink`.
    - Add an app-server sink that converts extension `ThreadGoalUpdated`
    events into `ServerNotification::ThreadGoalUpdated` broadcasts.
    - Use the existing bounded outgoing message channel via `try_send` so
    event forwarding cannot create an unbounded queue.
    - Pass `NoopExtensionEventSink` in app-server tests that construct a
    `ThreadManager` without an app-server host.
    - Refresh `Cargo.lock` for the existing `codex-memories-extension`
    `codex-otel` dependency.
    
    ## Verification
    
    - `just test -p codex-app-server
    extensions::tests::app_server_event_sink_forwards_thread_goal_updates`
  • Wire metrics client into memories extension (#24567)
    ## Summary
    
    - let the memories extension capture the process-global OTEL metrics
    client at install time
    - keep app-server/TUI/exec extension construction APIs unchanged
    - store the metrics client for future memory metrics without emitting
    any metrics yet
    
    ## Test plan
    
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - Not run: tests/clippy per request; CI will cover them
  • Respect hook trust bypass during TUI startup (#24317)
    Fixes #24093.
    
    ## Why
    
    `--dangerously-bypass-hook-trust` is a supported CLI flag intended for
    headless or automated runs where enabled hooks should be allowed to run
    without requiring persisted trust. In the TUI, startup hook review still
    opened whenever hooks looked untrusted, so a launch using the bypass
    could block on the interactive "Hooks need review" prompt.
    
    The tricky case is persistent app-server resume: a resume may attach to
    an already-running thread, where resume config overrides are ignored. In
    that path, hiding the startup review would be wrong because the existing
    hook engine may still filter untrusted hooks.
    
    ## What Changed
    
    - Startup hook review now skips the prompt only when hook trust bypass
    is actually safe for that launch.
    - The TUI forwards `bypass_hook_trust` through the app-server request
    config for fresh thread start/resume/fork paths, and the app-server
    applies it as a runtime-only `ConfigOverrides` value rather than
    treating it like a `config.toml` setting.
    - Persistent app-server resumes keep the startup review prompt so users
    still have a chance to trust hooks when the running thread cannot
    receive the bypass override.
    
    ## Verification
    
    - Added focused coverage for startup hook review with and without
    `bypass_hook_trust`.
    - Extended existing TUI/app-server config override tests to cover
    forwarding and applying `bypass_hook_trust`.
  • 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`
  • Add new enterprise requirement gate (#23736)
    Add new enterprise requirement gate.
    
    Validation:
    - `cargo test -p codex-config --lib`
    - `cargo test -p codex-app-server-protocol --lib`
    - `cargo test -p codex-tui --lib debug_config`
    - `cargo test -p codex-app-server --lib` *(fails: stack overflow in
    `in_process::tests::in_process_start_initializes_and_handles_typed_v2_request`;
    reproduces when run alone)*
  • app-server: drop legacy profile config surface (#24067)
    ## Why
    
    Legacy `[profiles.<name>]` config tables and the legacy `profile`
    selector are being retired in favor of profile files selected with
    `--profile <name>`. After #23886 removed the CLI-side legacy profile
    plumbing, the app-server config surface still exposed those fields and
    still carried conversion code for the old protocol shape.
    
    ## What changed
    
    - Remove `profile`, `profiles`, and `ProfileV2` from the app-server
    config protocol/schema output so `config/read` no longer returns legacy
    profile config.
    - Drop the old v1 `UserSavedConfig` profile conversion path from
    `config`.
    - Reject new app-server config writes under `profiles.*` with the same
    migration direction used for `profile`, while still allowing callers to
    clear existing legacy profile tables.
    - Refresh app-server config coverage and the experimental API README
    example around the remaining `Config` nesting path.
    
    ## Verification
    
    - Added config-manager coverage that `config/read` omits legacy profile
    config, `profiles.*` writes are rejected, and existing legacy profile
    tables can still be cleared.
    - Updated the v2 config RPC test to cover the rejected `profiles.*`
    batch-write path.
  • fix: reject legacy profile selectors (#24059)
    ## Why
    
    `--profile` now selects `<name>.config.toml`, so the legacy `profile`
    selector should not be reintroduced through config write or MCP tool
    paths. A matching legacy selector in base user config also needs the
    same migration guard as a matching legacy `[profiles.<name>]` table so
    profile loading fails with one clear migration error instead of mixing
    the old and new profile models.
    
    ## What
    
    - reject non-null app-server config writes to the top-level legacy
    `profile` selector
    - make `--profile <name>` reject base user config that still selects the
    same legacy `profile = "<name>"` value, alongside the existing matching
    legacy profile-table guard
    - reject removed MCP `codex` tool fields such as `profile` by denying
    unknown tool-call parameters and exposing that restriction in the
    generated schema
    - add regression coverage for the app-server write paths, config loader
    guard, and MCP tool input/schema behavior
    
    ## Verification
    
    - targeted regression tests cover the new app-server, config loader, and
    MCP rejection paths
  • config: remove legacy profile write paths (#24055)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2 and
    [#23886](https://github.com/openai/codex/pull/23886) removed CLI
    forwarding for the legacy profile-v1 path. Core and TUI config
    persistence still carried `active_profile` and
    `ConfigEditsBuilder::with_profile`, which let later writes continue
    targeting legacy `[profiles.<name>]` tables after profile selection
    moved to profile-v2 config files.
    
    ## What
    
    - Remove legacy profile routing from
    [`ConfigEditsBuilder`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/core/src/config/edit.rs#L1064-L1294),
    so core config edits no longer carry `with_profile` or infer
    `[profiles.*]` write targets from a `profile` key.
    - Drop `active_profile` plumbing from runtime `Config`, TUI
    startup/state, app-server config override forwarding, and Windows
    sandbox setup persistence.
    - Make app-server-backed TUI config edits use unscoped model,
    service-tier, feature, Auto-review, plan-mode, and Windows sandbox paths
    through
    [`tui/src/config_update.rs`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/tui/src/config_update.rs#L43-L112).
    - Update config edit coverage so legacy `profile` state stays untouched
    by direct model writes, and remove tests whose only contract was the
    deleted profile-scoped persistence path.
    
    ## Testing
    
    - Not run locally.
  • fix(remote-control): retry after auth recovery (#23775)
    ## Why
    
    When remote control hits an auth failure such as a revoked or reused
    refresh token, the websocket loop falls into reconnect backoff. If the
    user fixes auth while that loop is sleeping, remote control can stay
    offline until the old retry timer expires because nothing wakes the loop
    or resets its exhausted auth recovery state.
    
    ## What Changed
    
    Added an auth-change watch on `AuthManager` for refresh-relevant cached
    auth updates.
    
    The remote-control websocket loop now subscribes to that signal, resets
    `UnauthorizedRecovery` and reconnect backoff when auth changes, and
    retries immediately instead of waiting for the previous delay.
    
    Updated the remote-control transport test to verify that reloading auth
    with the now-available account id wakes enrollment before the prior
    retry delay.
    
    ## Verification
    
    `cargo test -p codex-app-server-transport
    remote_control_waits_for_account_id_before_enrolling`
  • Remove plugin hooks feature flag (#22552)
    # Why
    
    This is a follow-up stacked on top of the `plugin_hooks` default-on
    change. Once we are comfortable making plugin hooks part of the normal
    plugin behavior, the separate feature flag stops buying us much and
    leaves extra branching/cache state behind.
    
    # What
    
    - remove the `PluginHooks` feature and generated config-schema entries
    - make plugin hook loading/listing follow plugin enablement directly
    - drop plugin-manager cache/state that only existed to distinguish
    hook-flag toggles
    - remove tests and fixtures that modeled `plugin_hooks = true/false`
  • [codex] Add rollout-backed thread content search (#23519)
    ## Summary
    - add experimental `thread/search` for local rollout-backed thread
    search using `rg` over JSONL rollouts
    - return search-specific result rows with optional previews instead of
    storing preview data on `StoredThread` or ordinary `Thread` responses
    - keep `thread/list` separate from full-content search and document the
    new app-server surface
    
    ## Testing
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_search_returns_content_and_title_matches -- --nocapture`
  • 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`
  • feat: support managed permission profiles in requirements.toml (#23433)
    ## Why
    
    Cloud-managed `requirements.toml` should be able to define the managed
    permission profiles a client may select and constrain that selectable
    set without requiring local user config to recreate the profile catalog.
    
    This keeps requirements focused on restrictions. The selected default
    remains a config or session choice, while requirements contribute the
    managed profile bodies and `allowed_permissions` allowlist that the
    config-loading boundary validates before a resolved runtime
    `PermissionProfile` is installed.
    
    ## What changed
    
    - Add `requirements.toml` support for a managed permission-profile
    catalog plus its allowlist:
    
    ```toml
    allowed_permissions = ["review", "build"]
    
    [permissions.review]
    extends = ":read-only"
    
    [permissions.build]
    extends = ":workspace"
    ```
    
    - Merge requirements-defined profile bodies into the effective
    permission catalog and reject profile ids that collide with
    config-defined profiles.
    - Validate that every `allowed_permissions` entry resolves to a built-in
    or catalog profile before selection uses it.
    - Preserve allowed configured named-profile selections. When a
    configured named profile is disallowed, fall back to the first allowed
    requirements profile with a startup warning.
    - Keep built-in selections and the stock trust-based `:read-only` /
    `:workspace` fallback path intact when no permission profile is
    explicitly selected.
    - Centralize the managed catalog and allowlist selection path in
    `EffectivePermissionSelection` so the requirements boundary is visible
    in config loading.
    - Surface `allowedPermissions` through `configRequirements/read`, and
    update the generated app-server schema fixtures plus the app-server
    README.
    
    ## Validation
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core system_requirements_`
    - `cargo test -p codex-core system_allowed_permissions_`
    - `cargo test -p codex-app-server-protocol`
    - `just write-app-server-schema`
    
    ## Related work
    
    - Uses merged permission-profile inheritance support from #22270 and
    #23705.
    - Kept separate from the in-flight permission profile listing API in
    #23412.
  • Use named MITM permissions config (#18240)
    ## Stack
    1. Parent PR: #18868 adds MITM hook config and model only.
    2. Parent PR: #20659 wires hook enforcement into the proxy request path.
    3. This PR changes the user facing PermissionProfile TOML shape.
    
    ## Why
    1. The broader goal is to make MITM clamping usable from the same
    permission profile that already controls network behavior.
    2. This PR is the config UX layer for the stack. It moves MITM policy
    into `[permissions.<profile>.network.mitm]` instead of exposing the flat
    runtime shape to users.
    3. The named hook and action tables belong here because users need
    reusable policy blocks that are easy to review, while the proxy runtime
    only needs a flat hook list.
    4. This PR validates action refs during config parsing so mistakes in
    the user facing policy fail before a proxy session starts.
    5. Keeping the lowering here lets the proxy keep its simpler runtime
    model and lets PermissionProfile remain the single source of network
    permission policy.
    
    ## Summary
    1. Keep MITM policy inside `[permissions.<profile>.network.mitm]` so the
    selected PermissionProfile owns network proxy policy.
    2. Use named MITM hooks under
    `[permissions.<profile>.network.mitm.hooks.<name>]`.
    3. Put host, methods, path prefixes, query, headers, body, and action
    refs on the hook table.
    4. Define reusable action blocks under
    `[permissions.<profile>.network.mitm.actions.<name>]`.
    5. Represent action blocks with `NetworkMitmActionToml`, then lower them
    into the proxy runtime action config.
    6. Reject unknown refs, empty refs, and empty action blocks during
    config parsing.
    7. Keep the runtime hook model unchanged by lowering config into the
    existing proxy hook list.
    8. Preserve the #20659 activation fix for nested MITM policy.
    
    ## Example
    ```toml
    [permissions.workspace.network.mitm]
    enabled = true
    
    [permissions.workspace.network.mitm.hooks.github_write]
    host = "api.github.com"
    methods = ["POST", "PUT"]
    path_prefixes = ["/repos/openai/"]
    action = ["strip_auth"]
    
    [permissions.workspace.network.mitm.actions.strip_auth]
    strip_request_headers = ["authorization"]
    ```
    
    ## Validation
    1. Regenerated the config schema.
    2. Ran the core MITM config parsing and validation tests.
    3. Ran the core PermissionProfile MITM proxy activation tests.
    4. Ran the core config schema fixture test.
    5. Ran the network proxy MITM policy tests.
    6. Ran the scoped Clippy fixer for the network proxy crate.
    7. Ran the scoped Clippy fixer for the core crate.
    
    ---------
    
    Co-authored-by: Winston Howes <winston@openai.com>
  • [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.
  • Add thread/settings/update app-server API (#23502)
    ## Why
    
    App-server clients need a way to update a thread's next-turn settings
    without starting a turn, adding transcript content, or waiting for turn
    lifecycle events. This gives settings UI a direct path for durable
    thread settings while clients observe the eventual effective state
    through a notification.
    
    This is a simplified rework of PR
    https://github.com/openai/codex/pull/22509. In particular, it changes
    the `thread/settings/update` api to return immediately rather than
    waiting and returning the effective (updated) thread settings. This
    makes the new api consistent with `turn/start` and greatly reduces the
    complexity of the implementation relative to the earlier attempt.
    
    ## What Changed
    
    - Adds experimental `thread/settings/update` with partial-update request
    fields and an empty acknowledgment response.
    - Adds experimental `thread/settings/updated`, carrying full effective
    `ThreadSettings` and scoped by `threadId` to subscribed clients for the
    affected thread.
    - Shares durable settings validation with `turn/start`, including
    `sandboxPolicy` plus `permissions` rejection and `serviceTier: null`
    clearing.
    - Emits the same settings notification when `turn/start` overrides
    change the stored effective thread settings.
    - Regenerates app-server protocol schema fixtures and updates
    `app-server/README.md`.
  • fix(app-server): speed up shutdown (#23578)
    ## Why
    
    Pressing `Ctrl+C` or `Ctrl+D` in the TUI could make Codex pause during
    shutdown when app-server background work still held outbound sender
    clones.
    
    Shutdown tracing against the current `~/.codex` path found three
    relevant holders:
    
    - `SkillsWatcher` kept its event-loop task alive until the shutdown
    timeout path.
    - `AppServerAttestationProvider` retained a strong
    `Arc<OutgoingMessageSender>`, which could keep outbound teardown waiting
    after the processor task had exited.
    - A background `apps/list` task could still own an outbound sender when
    shutdown began, causing the in-process app-server runtime to wait for
    its outbound channel to close.
    
    ## What Changed
    
    - Give `SkillsWatcher` an explicit shutdown `CancellationToken` and
    cancel it from app-server teardown so its event loop drops the outbound
    sender promptly.
    - Change `AppServerAttestationProvider` to keep a
    `Weak<OutgoingMessageSender>` and return immediately when it can no
    longer be upgraded.
    - Give `AppsRequestProcessor` a shutdown `CancellationToken` and cancel
    in-flight background `apps/list` work during teardown.
    
    ## How to Test
    
    1. Start Codex TUI from a real home configuration.
    2. Press `Ctrl+C`.
    3. Confirm Codex exits promptly instead of pausing during shutdown.
    4. Repeat with `Ctrl+D` and confirm the same prompt exit path.
    
    Focused manual trace validation from the investigation:
    
    - Before the full fix, reproduced shutdown traces showed outbound
    teardown waiting on lingering owners, including `attestation.provider=1`
    and later `apps.list.task=1`.
    - After the fix, fresh real-home `Ctrl+D` traces showed
    `app_server.runtime.outbound_state_after_processor_join` with
    `owners=none`, `app_server.runtime.wait_outbound_handle = 0ms`, and
    total TUI app-server shutdown around `18ms`.
    
    Targeted validation:
    
    - `RUST_MIN_STACK=8388608 cargo test -p codex-app-server`
  • feat: rename 3 (#23669)
    Just a mechanical renaming
  • feat: Add vertical remote plugin collection support (#23584)
    - Adds an explicit vertical marketplace kind for plugin/list that
    fail-open fetches collection=vertical only when full remote plugins are
    disabled.
    
    - Renames the global remote marketplace/cache identity to
    openai-curated-remote and materializes remote installs with backend
    release versions and app manifests.
  • feat: add permission profile list api (#23412)
    ## Why
    
    Clients need a typed permission-profile catalog instead of
    reconstructing that state from config internals.
    
    ## What changed
    
    - Added `permissionProfile/list` to the app-server v2 protocol with
    cursor pagination and optional `cwd`.
    - The list response includes built-in permission profiles plus
    config-defined `[permissions.<id>]` profiles from the effective config
    for the request context.
    - Permission profiles keep optional `description` metadata for display
    purposes.
    - App-server docs and schema fixtures are updated for the new RPC.
  • feat: expose codex-app-server version flag (#23593)
    ## Why
    
    `codex-app-server` is published as a standalone release binary, so it
    should support the same basic version inspection behavior users expect
    from command-line tools. This is independent of package assembly:
    package metadata now comes from `codex-rs/Cargo.toml`, but the
    standalone app-server binary should still answer `--version` directly.
    
    ## What changed
    
    - Enables Clap's generated `--version` flag for the `codex-app-server`
    binary by adding `#[command(version)]` to its top-level parser.
    
    ## Verification
    
    - Ran `cargo run -p codex-app-server --bin codex-app-server --
    --version` and verified it prints `codex-app-server 0.0.0`.
  • Add CUA requirements subsection for locked computer use (#23555)
    Adds a new top-level section for "CUA" requirements that can allow for
    disablement of specific features as needed for enterprises.
  • fix: serialize unix app-server startup (#23516)
    # Summary
    
    Unix-socket app-server startup can currently race when multiple launch
    attempts target the same `CODEX_HOME`. Those processes can overlap
    before the control socket exists, which lets them enter SQLite state
    initialization concurrently and reproduce the startup corruption pattern
    seen in SSH mode.
    
    This change makes the app-server own that singleton startup guarantee.
    Unix-socket startup now takes a `CODEX_HOME`-scoped advisory lock before
    SQLite initialization, runs the existing control-socket preparation
    check while holding that lock, returns the established `AddrInUse` error
    when another live listener already owns the socket, and releases the
    lock once the new listener has bound its socket.
    
    # Design decisions
    
    - The singleton rule lives in `app-server --listen unix://`, not in a
    desktop-only caller path, so every Unix-socket launch gets the same race
    protection.
    - A duplicate raw app-server launch returns an error instead of silently
    succeeding. The attach operation remains `app-server proxy`, which
    continues to connect to an already-running listener.
    - The lock is held only across the dangerous startup window: socket
    preparation, SQLite initialization, and socket bind. It is not held for
    the app-server lifetime.
    - Listener detection stays in `prepare_control_socket_path(...)`, so the
    preexisting live-listener and stale-socket behavior remains the single
    source of truth.
    
    # Testing
    
    Tests: targeted Unix-socket transport tests on the branch checkout, full
    `codex-cli` build on `efrazer-db10`, and an SSH-style smoke on
    `efrazer-db10` covering concurrent app-server starts, explicit
    duplicate-start errors, and absence of SQLite startup-error matches in
    launch logs.