Commit Graph

558 Commits

  • fix(tui): scope MCP startup status by thread (#26639)
    ## Why
    
    MCP startup failures from spawned subagents were rendered as global
    notifications, so a child thread's failure could pollute the visible
    parent transcript. Routing the notification to the child exposed two
    related replay problems: session refresh could discard the buffered
    event, and a newly created child `ChatWidget` did not know the expected
    MCP server set, which could leave its startup spinner running after
    every server had settled.
    
    MCP startup diagnostics should remain visible in the thread that owns
    the startup without affecting other transcripts. The protocol also needs
    to support a future app-scoped MCP lifecycle where startup is not owned
    by any thread.
    
    ## Reported Behavior
    
    The [originating Slack
    report](https://openai.slack.com/archives/C08JZTV654K/p1780604538859939)
    called out that using subagents could turn MCP startup failures into a
    wall of yellow CLI warnings because repeated failures were not
    deduplicated. The intended behavior is for those diagnostics to remain
    visible once in the thread that owns the startup, without polluting the
    parent transcript.
    
    ## What Changed
    
    - add nullable `threadId` ownership to `mcpServer/startupStatus/updated`
    - populate it from the app-server conversation ID for the current
    thread-scoped lifecycle and regenerate the protocol schema and
    TypeScript artifacts
    - treat a missing or null `threadId` as app-scoped without injecting it
    into the active chat transcript
    - route and buffer thread-owned MCP startup notifications by thread in
    the TUI
    - preserve buffered MCP startup events across child session refresh
    - seed expected MCP servers before replaying a thread snapshot so
    startup reaches its terminal state
    - suppress an identical repeated failure warning for the same server
    within one startup round
    
    The owning thread still renders the detailed failure and final `MCP
    startup incomplete (...)` summary.
    
    ## How to Test
    
    1. Configure an optional MCP server named `smoke` that exits during
    initialization.
    2. Launch the TUI with multi-agent support enabled.
    3. Confirm the main thread's own startup failure renders one detailed
    `smoke` warning and one incomplete-startup summary.
    4. Spawn exactly one subagent.
    5. Confirm the parent transcript does not receive the subagent's MCP
    startup failure.
    6. Switch to the subagent thread and confirm it contains exactly one
    detailed `smoke` failure and one incomplete-startup summary.
    7. Confirm the subagent's MCP startup spinner disappears and the thread
    remains usable.
    8. Switch between the parent and subagent and confirm the warnings
    neither move nor duplicate.
    
    Targeted tests:
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    thread_start_emits_mcp_server_status_updated_notifications`
    - `just test -p codex-tui mcp_startup`
    
    The parent/child behavior and spinner completion were also exercised
    manually in tmux. `just argument-comment-lint` was attempted but blocked
    by an unrelated local Bazel LLVM empty-glob failure; touched Rust
    callsites were inspected manually.
  • permissions: enforce managed permission profile allowlists (#24852)
    ## Why
    
    Permission profile allowlists are an enterprise security boundary, but
    they also need to compose across the managed requirements layers added
    in #24620.
    
    A map representation lets each requirements layer add, allow, or revoke
    individual profiles without replacing an entire array.
    
    ## Managed Contract
    
    Administrators configure the mergeable allow map with
    `allowed_permission_profiles`. A recommended enterprise configuration
    explicitly lists every built-in and custom profile users should be able
    to select:
    
    ```toml
    default_permissions = "review_only"
    
    [allowed_permission_profiles]
    ":read-only" = true
    ":workspace" = true
    review_only = true
    # ":danger-full-access" is intentionally omitted, so it is denied.
    
    [permissions.review_only]
    extends = ":read-only"
    ```
    
    - Profiles whose effective merged value is `true` are allowed.
    - Missing profiles and profiles set to `false` are denied.
    - This is a closed allowlist: built-in profiles and profiles introduced
    in future versions are denied unless explicitly allowed.
    - Explicitly list each built-in profile the enterprise wants to make
    available. Omit built-ins such as `:danger-full-access` when they should
    remain unavailable.
    - Set `default_permissions` explicitly to the allowed profile users
    should receive when they have no local selection.
    - Higher-precedence layers override only the profile keys they define.
    - `false` is only needed when a higher-precedence layer must revoke a
    `true` inherited from a lower layer.
    - Explicit keys must refer to known built-in or managed profiles.
    
    A custom or narrowed allowlist requires an allowed
    `default_permissions`. For compatibility, if both `:workspace` and
    `:read-only` are explicitly allowed, an omitted default resolves to
    `:workspace`; customer configurations should still set the intended
    default explicitly.
    
    When `allowed_permission_profiles` is absent, existing implicit
    permission and legacy `sandbox_mode` behavior is unchanged.
    
    ## What Changed
    
    - Add `allowed_permission_profiles` as a `BTreeMap<String, bool>` that
    merges per profile across requirements layers.
    - Enforce managed defaults, strict denial of omitted profiles, and the
    explicitly allowed standard-pair fallback.
    - Expose `allowedPermissionProfiles` through `configRequirements/read`
    and regenerate its schemas.
    - Add regression coverage for map composition and revocation, managed
    defaults, strict denial of omitted built-ins, and API output.
    
    ## Verification
    
    - Focused `codex-config` coverage for layered map composition and
    revocation
    - Focused `codex-core` coverage for managed defaults, invalid defaults,
    strict denial of omitted built-ins, and the standard built-in pair
    - Focused `codex-app-server` coverage for requirements API output
    - Scoped Clippy for `codex-config`, `codex-core`,
    `codex-app-server-protocol`, and `codex-app-server`
    
    ## Documentation
    
    The managed `requirements.toml` documentation should introduce
    `allowed_permission_profiles` as a closed permission-profile allowlist
    before this setting is published on developers.openai.com.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex-rs] support v2 personal access tokens (#25731)
    ## Summary
    
    - add v2 personal access token support for `codex login
    --with-access-token` and `CODEX_ACCESS_TOKEN`
    - classify opaque `at-` tokens separately from legacy Agent Identity
    JWTs
    - hydrate required ChatGPT account metadata through AuthAPI
    `/v1/user-auth-credential/whoami`
    - use PATs directly as bearer tokens while preserving existing ChatGPT
    account surfaces
    - expose PAT-backed auth as the explicit `personalAccessToken`
    app-server auth mode
    
    ## Implementation
    
    PAT auth is intentionally small and stateless. Loading a PAT performs
    one AuthAPI metadata request, stores the hydrated metadata in the
    in-memory auth object, and redacts the secret from debug output. Legacy
    Agent Identity JWT handling remains unchanged. The shared access-token
    classifier lives in a private neutral module because it dispatches
    between both credential types.
    
    PAT hydration fails closed when AuthAPI omits any required metadata,
    including email. Hydrated metadata is intentionally not persisted:
    startup performs a live `whoami` preflight so revoked tokens or changed
    account metadata are not accepted from a stale cache.
    
    ## Workspace restriction scope
    
    This change intentionally does **not** apply
    `forced_chatgpt_workspace_id` to PAT authentication. The setting is a
    client-side config guardrail, not an authorization boundary, and PAT
    does not currently require workspace-ID parity. The PAT login and
    `CODEX_ACCESS_TOKEN` paths therefore validate through AuthAPI without
    threading workspace-restriction state through access-token loading.
    Existing workspace checks for non-PAT auth remain on their established
    paths.
    
    ## App-server compatibility
    
    The public app-server `AuthMode` is shared across v1 and v2, and
    PAT-backed auth reports `personalAccessToken` through both APIs.
    Following human review, this intentionally removes the temporary v1
    compatibility mapping that reported PATs as `chatgpt`; the deprecated v1
    API is kept in parity with v2 rather than maintaining a separate closed
    enum. Clients with exhaustive auth-mode handling in either API version
    must add the new case and should generally treat it as ChatGPT-backed
    unless they need PAT-specific behavior.
    
    The v1 auth-status response still omits the raw PAT when `includeToken`
    is requested because that response cannot carry the account metadata
    needed to reuse the credential safely. Persisted PAT auth also omits the
    new enum value so older Codex builds can deserialize `auth.json` and
    infer PAT auth from the credential field after a rollback.
    
    ## Validation
    
    Latest review-fix validation:
    
    - `CARGO_INCREMENTAL=0 just test -p codex-login` (126 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli` (263 passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-cli
    stored_auth_validation_handles_personal_access_token`
    - `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol` (226
    passed)
    - `CARGO_INCREMENTAL=0 just test -p codex-models-manager
    refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth`
    - `CARGO_INCREMENTAL=0 just test -p codex-tui
    existing_non_oauth_chatgpt_login_counts_as_signed_in`
    - `CARGO_INCREMENTAL=0 just fix -p codex-login -p
    codex-app-server-protocol -p codex-models-manager -p codex-tui -p
    codex-cli`
    - `just fmt`
    - `git diff --check`
    
    The broader `codex-tui` suite previously compiled and ran 2,834 tests.
    Three unrelated environment-sensitive guardian/IDE-socket tests failed
    after retries; the PAT-relevant TUI coverage passed.
  • Make runtime workspace roots absolute in app-server API (#26552)
    Stacked on #26532.
    
    ## Why
    
    #26532 moves cwd normalization to the app-server/core boundary.
    `runtimeWorkspaceRoots` still accepted raw paths in v2 requests and in
    `ConfigOverrides`, which left core responsible for interpreting those
    roots later. This makes runtime workspace roots follow the same
    absolute-path boundary as cwd.
    
    ## What
    
    - Change v2 `runtimeWorkspaceRoots` request fields for `thread/start`,
    `thread/resume`, `thread/fork`, and `turn/start` to `AbsolutePathBuf`.
    - Deduplicate already-absolute runtime roots in app-server handlers and
    pass them through `ConfigOverrides.workspace_roots` as
    `AbsolutePathBuf`.
    - Update TUI and exec client request builders to pass absolute runtime
    roots directly.
    - Update app-server docs, schema fixtures, and focused tests for
    absolute runtime roots.
    
    ## Testing
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server runtime_workspace_roots`
    - `just test -p codex-core
    session_permission_profile_rebinds_runtime_workspace_roots`
    - `just test -p codex-tui app_server_session`
    - `just test -p codex-exec`
  • feat(app-server): add remote control pairing status RPC (#26450)
    ## What
    
    Exposes the pairing status transport as experimental app-server v2 RPC
    `remoteControl/pairing/status`.
    
    - Adds request/response protocol types for exactly one lookup key:
    `pairingCode` or `manualPairingCode`, returning `{ claimed }`.
    - Registers the RPC with `global_shared_read("remote-control-pairing")`.
    - Wires the method through `MessageProcessor` and
    `RemoteControlRequestProcessor`.
    - Validates missing/conflicting pairing-code params as invalid requests.
    - Documents the RPC in `app-server/README.md`.
    - Adds processor, protocol export, and JSON-RPC integration coverage for
    both code paths.
    
    ## Why
    
    This is the app-server surface the desktop app can poll while the
    QR/manual pairing modal is active.
    
    Depends on https://github.com/openai/codex/pull/26449
    Related backend change: https://github.com/openai/openai/pull/990244
    
    ## Verification
    
    - `cargo test --manifest-path app-server-protocol/Cargo.toml
    remote_control`
    - `cargo test --manifest-path app-server/Cargo.toml remote_control`
    - `cargo fmt --all --check`
    - `git diff --check`
  • feat(remote-control): add pairing status transport (#26449)
    ## What
    
    Adds transport support for checking remote-control pairing status
    against the backend.
    
    - Adds the normalized `server/pair/status` backend URL.
    - Adds backend request/response structs for exactly one lookup key:
    `pairing_code` or `manual_pairing_code`, returning `{ claimed }`.
    - Adds `RemoteControlEnrollment::pairing_status` and
    `RemoteControlHandle::pairing_status`.
    - Preserves auth refresh/retry behavior and backend error mapping.
    - Adds transport coverage for pending, claimed, manual-code payloads,
    token refresh, mapped backend errors, malformed responses, and URL
    normalization.
    
    ## Why
    
    Desktop needs a host-authenticated way to poll whether a QR or manual
    pairing code has been claimed.
    
    Related backend change: https://github.com/openai/openai/pull/990244
    
    ## Verification
    
    - `cargo test --manifest-path app-server-transport/Cargo.toml
    remote_control::tests::pairing_tests`
    - `cargo fmt --all --check`
    - `git diff --check`
  • feat(app-server): expose account token usage [1 of 2] (#25344)
    ## Why
    
    Token activity is useful account-level context, but terminal clients
    need a supported app-server path to fetch it without reaching into
    ChatGPT backend details directly. The API should also live under the
    broader account usage umbrella so future usage surfaces can be added
    without proliferating user-facing concepts.
    
    ## What Changed
    
    - Add `codex-backend-client` support for the ChatGPT profile token-usage
    payload.
    - Add the v2 `account/usage/read` app-server RPC.
    - Map lifetime usage, peak daily usage, streak, longest task duration,
    and daily buckets into app-server protocol types.
    - Gate the request on Codex-backend auth, which supports ChatGPT auth
    tokens and AgentIdentity.
    - Regenerate the app-server JSON and TypeScript schema fixtures.
    
    ## Token Count Source
    
    `account/usage/read` returns the token-usage aggregate supplied by the
    ChatGPT profile backend. App-server maps that backend-owned aggregate
    into protocol fields; it does not recompute cached-token treatment,
    usage multipliers, or raw input/output totals locally.
    
    ## Stack
    
    1. feat(app-server): expose account token usage [1 of 2] (this PR)
    2. [#25345](https://github.com/openai/codex/pull/25345) feat(tui): add
    token activity command [2 of 2]
    
    ## How to Test
    
    1. Start an app-server client from this branch while authenticated with
    ChatGPT or AgentIdentity.
    2. Call `account/usage/read`.
    3. Confirm the response includes `summary` and `dailyUsageBuckets`.
    4. Also verify a session without Codex-backend auth receives the
    existing auth error path.
    
    Targeted tests:
    - `just test -p codex-backend-client -p codex-app-server-protocol -p
    codex-app-server`
    - `just write-app-server-schema`
  • [codex] Forward turn moderation metadata through app-server (#25710)
    ## Why
    First-party backends can supply turn-scoped moderation metadata that
    app-server clients need for client-side presentation. Exposing this as
    an experimental typed notification lets opted-in clients consume it
    without interpreting raw Responses API events.
    
    ## What changed
    - forward `response.metadata.openai_chatgpt_moderation_metadata` from
    Responses API SSE and WebSocket streams as turn-scoped moderation
    metadata
    - emit the experimental app-server v2 `turn/moderationMetadata`
    notification with `{ threadId, turnId, metadata }`
    - add app-server integration coverage for the typed moderation metadata
    notification
    
    ## Testing
    - `just test -p codex-core
    build_ws_client_metadata_includes_window_lineage_and_turn_metadata`
    - `just test -p codex-core` (fails locally: 46 failures and 1 timeout,
    primarily missing `test_stdio_server` and shell snapshot timeouts)
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    turn_moderation_metadata_emits_typed_notification_v2`
    - `just test -p codex-app-server` (fails locally: 792 passed, 10 failed,
    and 5 timed out; failures are in existing environment-sensitive tests,
    primarily because nested macOS `sandbox-exec` is not permitted)
    - `just write-app-server-schema --experimental --schema-root
    /tmp/codex-app-server-schema-experimental`
  • [codex] Expose unavailable app templates in plugin detail (#26317)
    ## Summary
    - Adds `unavailable_app_templates` to the app-server protocol and
    generated schemas/types.
    - Parses plugin-service `release.unavailable_app_templates` in the
    remote plugin client.
    - Maps remote unavailable templates into app-server `PluginDetail`.
    - Defaults local plugins to an empty unavailable app template list.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo +1.95.0 fmt --manifest-path codex-rs/Cargo.toml --all --check`
    - `cargo +1.95.0 test --manifest-path codex-rs/Cargo.toml -p
    codex-app-server-protocol schema_fixtures`
    - `cargo +1.95.0 check --manifest-path codex-rs/Cargo.toml -p
    codex-app-server-protocol -p codex-core-plugins -p codex-app-server`
    - `git diff --check`
    
    Note: default `cargo check` uses rustc 1.89 locally and failed because
    dependencies require newer Rust, so validation was rerun with installed
    Rust 1.95.
  • [codex] Support model-defined reasoning efforts (#26444)
    ## Summary
    - accept non-empty model-defined reasoning effort values while
    preserving built-in effort behavior
    - propagate the non-Copy effort type through core, app-server, TUI,
    telemetry, and persistence call sites
    - preserve string wire encoding and expose an open-string schema for
    clients
    - update model selection and shortcut behavior for model-advertised
    effort values
    
    ## Root cause
    `ReasoningEffort` gained a string-backed custom variant, so it could no
    longer implement `Copy` or rely on derived closed-enum serialization.
    Existing consumers still moved effort values from shared references and
    assumed a fixed built-in value set.
    
    ## Validation
    - `just fmt`
    - Local tests and compilation were not run per request; relying on CI.
  • [profile-switcher][rust] -- [1/2] Add app-server account session protocol (#25469)
    ## Summary
    
    Adds the app-server v2 `accountSession/*` protocol used by the Desktop
    profile switcher and the backend account metadata client needed to
    populate workspace choices.
    
    This is the protocol layer only. The app-server lifecycle and
    consolidated saved-session storage are split into a follow-up PR.
    
    ## Rust Stack
    
    1. This PR
    2. [openai/codex#25383](https://github.com/openai/codex/pull/25383) adds
    app-server session lifecycle behavior and consolidated saved-session
    storage.
    
    ## Validation
    
    - Generated app-server schema fixtures are included from the existing
    generation flow in the lifecycle PR where the routes are registered.
    - Did not run tests per requested scope.
  • feat(app-server): add remote control client management RPCs (#25785)
    ## Why
    
    Remote-control clients need to list and revoke controller-device grants
    without enabling or enrolling the local relay. These are signed-in
    account-management operations, so coupling them to websocket, pairing,
    enrollment, or persisted relay state would prevent clients from managing
    stale grants from the picker.
    
    Related enhancement request: N/A. This adds the Codex app-server surface
    for the planned upstream environment-scoped revoke endpoint.
    
    ## What Changed
    
    - Added experimental app-server v2 RPCs:
      - `remoteControl/client/list`
      - `remoteControl/client/revoke`
    - Added picker-oriented protocol types and standard generated schema
    fixtures. The list response intentionally omits backend account id,
    enrollment status, and location fields.
    - Added `app-server-transport/src/transport/remote_control/clients.rs`
    for environment-scoped GET and DELETE requests. It builds escaped URL
    path segments, forwards optional pagination query fields, sends ChatGPT
    auth plus `chatgpt-account-id`, converts RFC3339 `last_seen_at` values
    to Unix seconds, accepts `204 No Content` revoke responses, and retries
    once after a `401`.
    - Extracted shared ChatGPT auth loading and recovery into
    `app-server-transport/src/transport/remote_control/auth.rs` so
    websocket, pairing, and client management use the same account-auth
    boundary.
    - Retained the configured remote-control base URL on
    `RemoteControlHandle` and resolve management URLs lazily, preserving
    deferred validation while relay startup is disabled.
    - Registered list as `global_shared_read("remote-control-clients")` and
    revoke as `global("remote-control-clients")`.
    
    ## Verification
    
    - Added transport coverage proving list and revoke work while relay
    state is disabled, IDs are escaped, picker-only fields are returned,
    timestamps are converted, revoke accepts `204`, auth headers are
    forwarded, `401` retries exactly once, `403` is not retried, and
    malformed list payloads retain decode context.
    - Added an app-server integration test proving both JSON-RPC methods
    work before relay enablement and successful revoke returns `{}`.
    - Regenerated and validated experimental and standard app-server schema
    fixtures.
  • Propagate permission approval environment id (#25862)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. This PR (#25862) - Propagate permission approval environment id:
    carries the selected environment id through approval events, app-server
    requests, TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    This PR is stacked on #25858, and #25867 is stacked on this PR.
    
    ## Why
    
    PR2 lets the model bind a `request_permissions` call to a selected
    environment, but the approval event and client-facing request still
    needed to carry that binding. For CCA, the user-facing prompt and
    delegated approval path should know which environment the grant applies
    to instead of relying on cwd alone.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsEvent`.
    - Emit the selected environment id from core permission approval events.
    - Preserve the environment id through delegate forwarding, including
    cwd-based delegated requests.
    - Added `environmentId` to app-server permission approval params,
    generated schema/TypeScript artifacts, and README examples.
    - Preserve and display the environment id in TUI permission approval
    prompts.
    - Updated focused core, app-server protocol, and TUI conversion
    coverage.
    
    ## Testing
    
    Not run locally per instruction. Performed read-only `git diff --check`.
  • [app-server][core] Add connector-level Guardian reviewer overrides (#25167)
    Context: https://openai.slack.com/archives/C0B4JAF0Q2C/p1779912328647229
    
    ```
    approvals_reviewer = "auto_review"
    
    [apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
    enabled = true
    approvals_reviewer = "user"
    default_tools_approval_mode = "prompt"
    ```
    
    <img width="230" height="84" alt="Screenshot 2026-05-31 at 11 56 34 AM"
    src="https://github.com/user-attachments/assets/e319f8f7-0983-42a7-98cd-3302732fa406"
    />
    
    <img width="841" height="233" alt="Screenshot 2026-05-31 at 11 52 42 AM"
    src="https://github.com/user-attachments/assets/7ac76645-4e90-4d00-8242-f031146a22a5"
    />
    
    -------
    
    ```
    approvals_reviewer = "user"
    
    [apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
    enabled = true
    approvals_reviewer = "auto_review"
    default_tools_approval_mode = "prompt"
    ```
    <img width="195" height="83" alt="Screenshot 2026-05-31 at 12 02 27 PM"
    src="https://github.com/user-attachments/assets/3d374dc8-8aa2-466f-a13f-e4ed8567aa2e"
    />
    <img width="771" height="207" alt="Screenshot 2026-05-31 at 12 05 42 PM"
    src="https://github.com/user-attachments/assets/105c2575-68d6-4ca6-8e69-dc8c82da36a2"
    />
    
    
    
    ## Summary
    - add `apps.<connector_id>.approvals_reviewer` to override Guardian or
    user review routing per connected app
    - apply overrides across direct app MCP calls, delegated MCP prompts,
    and app-server MCP elicitation review while preserving global behavior
    for non-app MCP servers
    - expose and document the config through app-server v2 and generated
    schemas, while honoring global managed reviewer requirements
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • feat: show enterprise monthly credit limits in status (#24812)
    ## Summary
    
    Enterprise users can have an effective monthly credit limit, but Codex
    `/status` currently drops that metadata from the account-usage response.
    
    This change adds the optional `spend_control.individual_limit`
    projection to the existing rate-limit snapshot flow. The backend client
    reads the monthly limit, app-server exposes it as `individualLimit`, and
    the TUI renders a `Monthly credit limit` row through the existing
    progress-bar renderer.
    
    When the backend does not return an effective monthly limit, existing
    rate-limit behavior is unchanged.
    
    ## Existing backend state
    
    The account-usage backend already returns the effective monthly limit
    and current usage together:
    
    ```json
    {
      "spend_control": {
        "reached": false,
        "individual_limit": {
          "limit": "25000",
          "used": "8000",
          "remaining": "17000",
          "used_percent": 32,
          "remaining_percent": 68,
          "reset_after_seconds": 86400,
          "reset_at": 1778137680
        }
      }
    }
    ```
    
    Before this change, Codex projected rolling `primary` and `secondary`
    windows plus `credits`. It ignored `spend_control.individual_limit`, so
    app-server clients and `/status` could not render the monthly cap.
    
    The updated flow is:
    
    ```text
    account usage backend
      -> backend-client reads spend_control.individual_limit
      -> existing rate-limit snapshot carries optional individual_limit
      -> app-server exposes optional individualLimit
      -> TUI renders Monthly credit limit
    ```
    
    ## App-server contract
    
    `account/rateLimits/read` and sparse `account/rateLimits/updated`
    notifications now include an additive nullable
    `rateLimits.individualLimit` field:
    
    ```json
    {
      "individualLimit": {
        "limit": "25000",
        "used": "8000",
        "remainingPercent": 68,
        "resetsAt": 1778137680
      }
    }
    ```
    
    In an `account/rateLimits/read` response, `null` means no monthly limit
    is available. `account/rateLimits/updated` remains a sparse rolling
    notification: clients merge available values into their most recent
    `account/rateLimits/read` snapshot or refetch. Nullable account metadata
    in a rolling notification does not clear a previously observed value.
    
    ## Design decisions
    
    - Extend the existing rate-limit snapshot instead of introducing a
    separate request or wire-level update protocol.
    - Keep the Codex projection narrow: `/status` needs the effective limit,
    current usage, remaining percentage, and reset timestamp.
    - Render the monthly row through the existing progress-bar renderer,
    with one optional detail line for `8,000 of 25,000 credits used`.
    - Keep the backend response optional so existing accounts and older
    usage states preserve their current behavior.
    - Preserve cached monthly metadata when sparse rolling notifications
    omit it. Live account-usage reads remain authoritative and can clear a
    removed limit.
    
    ## Visual evidence
    
    ```text
     Monthly credit limit:   [██████████████░░░░░░] 68% left (resets 07:08 on 7 May)
                             8,000 of 25,000 credits used
    ```
    
    Snapshot:
    `codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_enterprise_monthly_credit_limit.snap`
    
    ## Testing
    
    Tests: generated app-server schema verification, protocol tests,
    backend-client tests, app-server integration coverage, TUI snapshot
    coverage, formatting, and workspace lint cleanup.
  • feat(remote-control): add pairing start (#25675)
    ## Why
    
    Remote control enrollment authorizes a desktop server, but app-server v2
    did not expose the follow-up pairing operation needed to mint a
    short-lived controller pairing artifact from that enrolled server.
    Clients need a narrow RPC that starts pairing without exposing the
    backend `serverId` or conflating pairing with websocket connection
    state.
    
    Issue: N/A; internal remote-control pairing API change.
    
    ## What Changed
    
    Added experimental app-server v2 `remoteControl/pairing/start` with
    `manualCode` input and `pairingCode`, nullable `manualPairingCode`,
    `environmentId`, and Unix-seconds `expiresAt` output. The method
    serializes under its own `global("remote-control-pairing")` scope and is
    documented in `app-server/README.md`.
    
    Extended the remote-control transport with private `/server/pair`
    request/response types and normalized `pair_url` handling. Pairing uses
    the current enrolled server bearer, refreshes that bearer when needed,
    keeps backend `server_id` private, validates returned `server_id` and
    `environment_id` against the current enrollment, and preserves backend
    status/header/body context for failures and malformed responses.
    
    Wired the request through `RemoteControlRequestProcessor` and
    `MessageProcessor`, mapping unavailable/disabled pairing to
    `invalid_request` and backend failures to internal errors.
    
    ## Verification
    
    - `just test -p codex-app-server-transport`
    - `just test -p codex-app-server
    remote_control_pairing_start_returns_pairing_artifacts`
  • app-server: remove experimental persist_extended_history bool flag (#25712)
    ## Summary
    
    Remove the dead experimental `persistExtendedHistory` app-server flag
    and collapse rollout persistence to the single policy app-server already
    used.
    
    ## What Changed
    
    - Removed `persistExtendedHistory` from v2 thread start/resume/fork
    params and deleted its deprecation notice path.
    - Removed the persistence-mode enums and plumbing through core, rollout,
    and thread-store.
    - Made rollout filtering mode-free, keeping the existing limited
    persisted-history behavior.
    
    ## Test Plan
    
    - `just write-app-server-schema`
    - `cargo nextest run --no-fail-fast -p codex-app-server-protocol
    schema_fixtures`
    - `cargo nextest run --no-fail-fast -p codex-app-server
    thread_shell_command_history_responses_exclude_persisted_command_executions`
    - `cargo nextest run --no-fail-fast -p codex-rollout -p
    codex-thread-store`
    - final `rg` for removed flag/type names
  • 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
  • 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
  • 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`
  • [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`
  • 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`
  • 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.
  • 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.
  • 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.
  • 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
  • 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(app-server): fix optional bool annotations (#24099)
    `#[serde(default)]` wasn't sufficient for our generated TS types to
    reflect that clients didn't have to set them. We also need
    `skip_serializing_if = "std::ops::Not::not"`. This is already a rule in
    our agents.md file.
  • [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`
  • 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.
  • [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.
  • feat(permissions): resolve permission profile inheritance (#22270)
    ## Stack
    
    This is the foundation PR for the permission-profile inheritance stack.
    
    - This PR adds config-level `extends` resolution and merge semantics.
    - Follow-up: #23705 applies resolved profiles at runtime and updates the
    active-profile protocol surfaces.
    
    ## Why
    
    Permission profiles are starting to carry enough policy that
    copy-pasting near-identical definitions becomes hard to review and easy
    to drift. Before the runtime can consume inherited profiles, the config
    layer needs one explicit resolver that can merge parent chains and
    reject unsafe or invalid inheritance shapes.
    
    ## What changed
    
    - Add `extends` to permission-profile TOML and resolve parent chains in
    inheritance order.
    - Merge inherited profile TOML with the existing config merge behavior
    while preserving the permission-specific normalization needed for
    network domain keys.
    - Keep parent descriptions out of resolved child profiles and record
    inherited profile names separately for downstream consumers.
    - Reject undefined parents, unsupported built-in parents, and
    inheritance cycles with targeted errors.
    - Cover resolver behavior with TOML fixture tests and refresh the
    generated config schema.
    
    ## Validation
    
    - `cargo test -p codex-config`
    - `cargo test -p codex-core permissions_profiles_`
  • 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`.
  • 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.
  • 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 empty rollout path app-server handling (#23400)
    ## Summary
    - Coerce `path: ""` to `None` at the v2 protocol params deserialization
    boundary for `thread/resume` and `thread/fork`.
    - Restore the pre-ThreadStore running-thread resume behavior: if
    `threadId` is already running, rejoin it by id and treat a non-empty
    `path` only as a consistency check; otherwise cold resume keeps `history
    > path > threadId` precedence.
    - Add protocol, resume, and fork regression coverage for empty path
    payloads; refresh app-server schema fixtures for the clarified params
    docs.
    
    ## Tests
    - `just fmt`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol
    thread_path_params_deserialize_empty_path_as_none`
    - `cargo test -p codex-app-server-protocol --test schema_fixtures`
    - `cargo test -p codex-app-server empty_path`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-app-server --test all
    thread_resume_rejects_mismatched_path_for_running_thread_id`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-app-server --test all
    thread_resume_uses_path_over_non_running_thread_id`
  • Add SubagentStart hook (#22782)
    # What
    
    `SubagentStart` runs once when Codex creates a thread-spawned subagent,
    before that child sends its first model request. Thread-spawned
    subagents use `SubagentStart` instead of the normal root-agent
    `SessionStart` hook.
    
    Configured handlers match on the subagent `agent_type`, using the same
    value passed to `spawn_agent`. When no agent type is specified, Codex
    uses the default agent type.
    
    Hook input includes the normal session-start fields plus:
    
    - `agent_id`: the child thread id.
    - `agent_type`: the resolved subagent type.
    
    `SubagentStart` may return `hookSpecificOutput.additionalContext`. That
    context is added to the child conversation before the first model
    request.
    
    # Lifecycle Scope
    
    Only thread-spawned subagents run `SubagentStart`.
    
    Internal/system subagents such as Review, Compact, MemoryConsolidation,
    and Other do not run normal `SessionStart` hooks and do not run
    `SubagentStart`. This avoids exposing synthetic matcher labels for
    internal implementation paths.
    
    Also the `SessionStart` hook no longer fires for subagents, this matches
    behavior with other coding agents' implementation
    
    # Stack
    
    1. This PR: add `SubagentStart`.
    2. #22873: add `SubagentStop`.
    3. #22882: add subagent identity to normal hook inputs.
  • Make deny canonical for filesystem permission entries (#23493)
    ## Why
    Filesystem permission profiles used `none` for deny-read entries, which
    is less direct than the action the entry actually represents. This
    change makes `deny` the canonical filesystem permission spelling while
    preserving compatibility for older configs that still send `none`.
    
    ## What changed
    - rename `FileSystemAccessMode::None` to `Deny`
    - serialize and generate schemas with `deny` as the canonical value
    - retain `none` only as a legacy input alias for temporary config
    compatibility
    - update filesystem glob diagnostics and regression coverage to use the
    canonical spelling
    - refresh config and app-server schema fixtures to match the new wire
    shape
    
    ## Validation
    - `cargo test -p codex-protocol`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-core config_toml_deserializes_permission_profiles
    --lib`
    - `cargo test -p codex-core
    read_write_glob_patterns_still_reject_non_subpath_globs --lib`
    
    Earlier in the session, a broad `cargo test -p codex-core` run reached
    unrelated pre-existing failures in timing/snapshot/git-info tests under
    this environment; the targeted surfaces touched by this PR passed
    cleanly.
  • Add body_after_prefix auto-compact token limit scope (#22870)
    ## Why
    
    `model_auto_compact_token_limit` has only been able to budget the full
    active context. That makes it hard to set a small "growth since
    compaction" budget for sessions that preserve a large carried window
    prefix: the preserved prefix can consume the whole budget and force
    immediate repeated compaction.
    
    This PR adds an opt-in `body_after_prefix` scope so callers can apply
    `model_auto_compact_token_limit` to sampled output and later growth
    after the current carried prefix, while still forcing compaction before
    the full model context window is exhausted.
    
    ## What changed
    
    - Adds `AutoCompactTokenLimitScope` with the existing `total` behavior
    as the default and a new `body_after_prefix` mode:
    [`config_types.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/protocol/src/config_types.rs#L24-L37).
    - Threads `model_auto_compact_token_limit_scope` through config loading,
    `Config`, `core-api`, and app-server v2 schema/TypeScript generation.
    - Records the first observed input-token count for a `body_after_prefix`
    compaction window and uses it as the baseline when deciding whether the
    scoped auto-compaction budget is exhausted:
    [`turn.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/core/src/session/turn.rs#L743-L781).
    - Keeps a hard context-window cap in `body_after_prefix`, so scoped
    budgeting cannot let the active context overrun the usable window.
    
    ## Verification
    
    Added compact-suite coverage for the two key behaviors:
    `body_after_prefix` does not re-compact just because the carried prefix
    is larger than the scoped budget, and it still compacts when the total
    active context reaches the configured context window:
    [`compact.rs`](https://github.com/openai/codex/blob/973806b1cb35792555bead994cb3ed94656eb171/codex-rs/core/tests/suite/compact.rs#L3003-L3128).
  • app-server: use profile ids in v2 permission params (#23360)
    ## Why
    
    The v2 app-server permission profile fields are experimental, but the
    previous migration kept a legacy object payload for profile selection.
    That made clients aware of server-owned `activePermissionProfile`
    metadata such as `extends`, and it kept a
    `legacy_additional_writable_roots` path even though
    `runtimeWorkspaceRoots` now owns runtime workspace-root selection.
    
    This PR makes the client contract match the intended model: clients
    select a permission profile by id, and the server resolves and reports
    active profile provenance in response payloads.
    
    Follow-up to #22611.
    
    ## What Changed
    
    - Changed `thread/start`, `thread/resume`, `thread/fork`, and
    `turn/start` permission profile selection to plain profile id strings.
    - Changed `command/exec.permissionProfile` to a plain profile id string
    for the same client/server ownership split.
    - Removed `PermissionProfileSelectionParams` and the legacy `{ type:
    "profile", modifications: [...] }` compatibility deserializer.
    - Updated app-server, TUI, and `codex exec` call sites to send only ids,
    while keeping `activePermissionProfile` as server response metadata.
    - Updated app-server docs and schema fixtures for the revised
    `command/exec.permissionProfile` shape.
    
    ## Verification
    
    - `cargo test -p codex-app-server-protocol`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-app-server`
    - `cargo test -p codex-exec`
    - `RUST_MIN_STACK=8388608 cargo test -p codex-tui`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23360).
    * #23368
    * __->__ #23360
  • feat(app-server): add optional thread_id to experimentalFeature/list (#23335)
    ## Why
    
    `experimentalFeature/list` reports effective feature enablement, but
    currently does not resolve it against a working directory where
    project-local config.toml files can exist and toggle on/off features
    when merged into the effective config after resolving the various config
    layers. That means we effectively (and incorrectly) ignore features set
    in project-local config.
    
    To address that, this PR exposes an optional `thread_id` param which
    allows us to load the thread's `cwd.
    
    ## Testing
    
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server experimental_feature_list`