Commit Graph

406 Commits

  • feat: use encrypted local secrets for MCP OAuth (#27541)
    ## Summary
    
    - store MCP OAuth credentials in the configured auth credential backend
    - support encrypted-local OAuth storage, including legacy keyring
    migration
    - propagate the credential backend through MCP refresh, session, CLI,
    and app-server paths
    
    ## Stack
    
    1. #27504 — config and feature flag
    2. #27535 — auth-specific secret namespaces
    3. #27539 — encrypted CLI auth storage
    4. this PR — encrypted MCP OAuth storage
    
    This is a parallel review stack; the original #17931 remains unchanged.
    
    ## Tests
    
    - `just test -p codex-rmcp-client` (the transport round-trip test passed
    after building the required `codex` binary and retrying)
    - `just test -p codex-mcp`
    - `just test -p codex-app-server
    refresh_config_uses_latest_auth_keyring_backend`
    - `just test -p codex-core
    refresh_mcp_servers_is_deferred_until_next_turn`
    - `just test -p codex-cli mcp`
    - `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
    -p codex-app-server -p codex-protocol`
    - `just bazel-lock-check`
  • feat: use encrypted local secrets for CLI auth (#27539)
    ## Why
    
    Windows Credential Manager limits generic credential blobs to 2,560
    bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
    keyring-mode CLI auth needs a backend that keeps only the encryption key
    in the OS keyring and stores the payload in Codex's encrypted
    local-secrets file.
    
    This is the third PR in the encrypted-auth stack:
    
    1. #27504 — feature and config selection
    2. #27535 — auth-specific local-secrets namespaces
    3. This PR — CLI auth implementation and activation
    4. MCP OAuth implementation and activation
    
    ## What Changed
    
    - Added encrypted CLI-auth storage using the `CliAuth` secrets
    namespace.
    - Preserved direct keyring storage for platforms/configurations where it
    remains selected.
    - Selected the backend consistently for login, logout, refresh,
    device-code login, auth loading, and login restrictions.
    - Threaded resolved bootstrap/full config through CLI, exec, TUI,
    app-server account handling, cloud config, and cloud tasks.
    - Removed stale `auth.json` fallback data after successful encrypted
    saves and removed encrypted, direct-keyring, and fallback data during
    logout.
    - Added storage and integration coverage for both direct and encrypted
    keyring modes.
    
    MCP OAuth persistence is intentionally left to the next PR.
    
    ## Validation
    
    - `just test -p codex-login` — 131 passed
    - `just test -p codex-cli` — 280 passed
    - `just test -p codex-app-server v2::account` — 25 passed
    - `just test -p codex-cloud-config service` — 21 passed, 7 skipped
    - `just fix -p codex-login`
    - `just fix -p codex-cli`
    - `just fmt`
  • [login] revoke existing auth before starting login (#27674)
    ## Why
    
    `codex login` previously persisted newly issued OAuth credentials and
    only then attempted to revoke the superseded refresh token. The old
    credential must be revoked before a replacement browser or device-code
    flow starts, and successful login must not perform any post-login
    revocation attempt.
    
    ## What changed
    
    - Revoke and clear existing stored auth before browser or device-code
    CLI login begins.
    - Remove superseded-token detection and revocation from the shared token
    persistence path; successful login now only saves the new credentials.
    - Read the raw configured auth store during CLI cleanup so
    environment-provided auth cannot mask the stored refresh token.
    - Preserve `auto` storage fallback semantics when keyring deletion fails
    by clearing the fallback auth file.
    - Add a process-level CLI regression test that requires the revoke
    request to precede every device-login request and occur exactly once.
    
    If replacement login is canceled or fails, the previous local
    credentials have already been cleared. Remote revocation remains best
    effort, matching explicit logout behavior.
    
    ## Validation
    
    ### Process-level before/after reproduction
    
    I compiled the real `codex` CLI from the pre-fix parent (`14df0e8833`)
    and from the PR implementation (`25c002f23b`; the login behavior is
    unchanged at the current head), then ran the same device-code flow
    against a local HTTP mock OAuth authority.
    
    Each run:
    
    1. Used a fresh temporary `CODEX_HOME` configured with
    `cli_auth_credentials_store = "file"`.
    2. Seeded that temporary home with managed ChatGPT auth containing
    `old-access` and `old-refresh` tokens.
    3. Pointed `CODEX_REVOKE_TOKEN_URL_OVERRIDE` at the mock `/oauth/revoke`
    endpoint.
    4. Ran the compiled CLI as:
    
       ```shell
       CODEX_HOME=<temporary-home> \
         CODEX_REVOKE_TOKEN_URL_OVERRIDE=<mock-issuer>/oauth/revoke \
    <compiled-codex> login --device-auth --experimental_issuer <mock-issuer>
       ```
    
    5. Recorded every request received by the mock authority. The mock
    marked `new-access` valid when `/oauth/token` issued it and invalidated
    it if `/oauth/revoke` arrived afterward, reproducing the observed
    session-invalidating failure mode. After login exited, the harness also
    verified the persisted refresh token and probed a protected endpoint
    with `new-access`.
    
    | Build | Observed request order | CLI/persistence result | `new-access`
    probe |
    | --- | --- | --- | --- |
    | Pre-fix | `usercode → device token → OAuth token →
    revoke(old-refresh)` | Exit `0`; `new-refresh` persisted | `401` |
    | PR | `revoke(old-refresh) → usercode → device token → OAuth token` |
    Exit `0`; `new-refresh` persisted | `200` |
    
    The PR run therefore issued exactly one revocation request, before any
    request that initiated the replacement login, and issued no revocation
    after token exchange.
    
    ### Regression coverage
    
    
    `codex-rs/cli/tests/login.rs::device_login_revokes_existing_auth_before_requesting_new_tokens`
    runs the real first-party `codex` binary against a `wiremock` OAuth
    server with an isolated temporary `CODEX_HOME`. It asserts:
    
    - the exact request sequence is `/oauth/revoke`,
    `/api/accounts/deviceauth/usercode`, `/api/accounts/deviceauth/token`,
    then `/oauth/token`;
    - there is exactly one revoke request and its body contains
    `old-refresh` with the `refresh_token` hint;
    - the completed login persists `new-refresh`.
    
    Local validation:
    
    - `just test -p codex-login` — 130 passed
    - `just test -p codex-cli` — 280 passed, including the new process-level
    regression test
    - `just bazel-lock-check`
  • feat(app-server): persist remote-control desired state (#27445)
    ## Why
    
    Remote-control runtime enablement and persisted enrollment preference
    were represented by separate flags. That made startup rehydration, RPC
    persistence, and new-enrollment seeding race with one another, and it
    did not cleanly distinguish runtime-only CLI or daemon starts from
    durable app-server RPC changes.
    
    ## What Changed
    
    - Replace the parallel enablement, seed, and rehydration flags with one
    transport-owned `RemoteControlDesiredState`.
    - Add nullable enrollment-scoped persistence and preserve existing
    preferences during enrollment upserts.
    - Rehydrate plain startup only after auth and client scope resolve,
    without overwriting a concurrent RPC transition.
    - Make ordinary `remoteControl/enable` and `remoteControl/disable`
    durable while retaining `ephemeral: true` for runtime-only callers.
    - Have the daemon explicitly request ephemeral enablement and regenerate
    the app-server schemas.
    
    ## Verification
    
    - Covered migration and `NULL`/`0`/`1` persistence round trips.
    - Covered plain-start rehydration and runtime-only versus durable
    enrollment seeding.
    - Covered durable enable, durable disable, and ephemeral enable through
    app-server RPC.
    - Covered the daemon's exact `{ "ephemeral": true }` request payload.
    
    Related issue: N/A (internal remote-control persistence architecture
    change).
  • [codex-rs] enforce PAT workspace restrictions (#27450)
    ## Summary
    
    - validate a hydrated personal access token's workspace against
    `forced_chatgpt_workspace_id` before persisting `codex login
    --with-access-token`
    - apply the same PAT-only check when restricted auth managers load
    environment, ephemeral, or persisted credentials
    - enforce PAT workspace restrictions in the existing central
    login-restriction path
    - leave Agent Identity and cloud bootstrap behavior unchanged
    
    ## Scope
    
    This is intentionally the small PAT-only change. It does not attempt the
    broader auth-manager/bootstrap unification; that needs separate design
    work.
    
    ## Validation
    
    - `CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=/tmp/codex-pat-target just test
    -p codex-login -p codex-cli` (410 passed)
    - `CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=/tmp/codex-pat-target just fix
    -p codex-login -p codex-cli`
    - `just fmt`
    - `git diff --check`
    
    Context: https://openai.slack.com/archives/D0AUPLV03RQ/p1781138331548269
  • [codex] Load user instructions through an injected provider (#27101)
    ## Why
    
    We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
    make embedders responsible for supplying user-level instructions. This
    also ensures user instructions load when no primary environment is
    selected.
    
    ## What changed
    
    Stacked on #27415, which makes `codex exec` surface thread-scoped
    runtime warnings.
    
    - Added `UserInstructionsProvider` to `codex-extension-api`, with
    absolute source attribution and recoverable loading warnings.
    - Added `codex-home` with the filesystem-backed provider for
    `AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
    trimming, lossy UTF-8 handling, and the existing uncapped global
    instruction size.
    - Removed global instruction loading from `Config` and require
    `ThreadManager` callers to inject a provider.
    - Load provider instructions once for each fresh root runtime, including
    runtimes without a primary environment. Running sessions retain their
    snapshot, while child agents inherit the parent snapshot without
    invoking the provider.
    - Keep provider instructions separate while loading project `AGENTS.md`,
    then assemble the model-visible instructions with the existing ordering,
    source attribution, warning, and turn-context behavior.
    - Wired the Codex home provider through the CLI, app server, MCP server,
    core facade, and thread-manager sample.
    
    ## Validation
    
    - `just test -p codex-home -p codex-extension-api`
    - `just test -p codex-core agents_md`
    - `just test -p codex-core guardian`
    - `just test -p codex-app-server
    thread_start_without_selected_environment_includes_only_global_instruction_source`
    - `just test -p codex-exec warning`
    - `just bazel-lock-check`
  • Print TUI session info on fatal exits (#27417)
    ## Summary
    
    TUI exits printed the resume/session summary only after checking the
    exit reason. On fatal exits, both CLI wrappers wrote the error and
    called `process::exit(1)` immediately, so an active session that ended
    on a fatal error could skip the session information entirely.
    
    This change prints the normal exit summary before returning the fatal
    nonzero exit code. If a fatal exit has a known thread id but no
    resumable rollout hint, it prints `Session ID: <id>` instead of staying
    silent. It also flushes stdout before `process::exit(1)` so the summary
    line is not lost during process teardown.
    
    ## Implementation
    
    - Apply the fatal-exit ordering fix in both `codex` and standalone
    `codex-tui`.
    - Keep normal user-requested exit behavior unchanged.
    - Preserve the existing resume hint when a rollout is resumable, and use
    the raw thread id only as a fatal-exit fallback.
  • feat: add Bedrock API key as a managed auth mode (#27443)
    ## Why
    
    Codex needs to manage Amazon Bedrock API key credentials through the
    existing auth lifecycle instead of introducing a separate auth manager
    or provider-specific credential file. Treating Bedrock API key login as
    a primary auth mode gives it the same persistence, keyring, reload, and
    logout behavior as the existing OpenAI API key and ChatGPT modes.
    
    The credential is valid only for the `amazon-bedrock` model provider.
    OpenAI-compatible providers must reject this auth mode rather than
    treating the Bedrock key as an OpenAI bearer token.
    
    ## What changed
    
    - Added `bedrockApiKey` as an app-server `AuthMode` and
    `CodexAuth::BedrockApiKey` as a primary `AuthManager` mode.
    - Added `BedrockApiKeyAuth`, containing the API key and AWS region, to
    the existing `AuthDotJson` payload stored in `$CODEX_HOME/auth.json` or
    the configured keyring backend.
    - Added `login_with_bedrock_api_key(...)`, parallel to
    `login_with_api_key(...)`, which replaces the current stored login with
    Bedrock credentials.
    - Reused generic auth reload and logout behavior instead of adding a
    Bedrock-specific auth manager or logout path.
    - Updated login restrictions, status reporting, diagnostics, telemetry
    classification, generated app-server schemas, and auth fixtures for the
    new mode.
    - Added explicit errors when Bedrock API key auth is selected with an
    OpenAI-compatible model provider.
    
    This PR establishes managed storage and auth-mode behavior. Routing the
    managed key and region into Amazon Bedrock requests will be in follow-up
    PRs.
  • Add session delete commands in CLI and TUI (#27476)
    ## Summary
    
    The app server exposes `thread/delete`, but users cannot invoke it from
    the CLI or TUI. Because deletion is irreversible, the user-facing
    commands need deliberate confirmation and safer handling of name-based
    targets.
    
    - Add `codex delete <SESSION>` with interactive confirmation,
    restricting `--force` to UUID targets.
    - Resolve exact names across active and archived sessions, including
    renamed sessions, and validate prompted UUID targets before
    confirmation.
    - Add a `/delete` command with a confirmation popup that warns the
    current session and its subagent threads will be permanently deleted.
    
    ## Manual testing
    
    - Deleted by UUID with `--force` and verified the rollout, session-index
    entry, and database row were removed.
    - Exercised name-based confirmation for both cancellation and
    affirmative deletion; cancellation preserved the session and
    confirmation removed it.
    - Verified deletion refuses to proceed without `--force`, while
    `--force` rejects names, including duplicate names.
    - Verified duplicate-name confirmation displays the concrete UUID
    selected.
    - Deleted an archived session by name.
    - Verified an already-missing UUID fails before displaying a
    confirmation prompt.
    - Exercised `/delete` in the TUI: the popup defaults to No, cancellation
    preserves the session, and confirmation deletes the session and exits.
    - Verified that `codex delete` works for both archived and non-archived
    sessions.
  • [codex] Skip local curated discovery for remote plugins (#27311)
    ## Summary
    
    - skip the local `openai-curated` marketplace before marketplace loading
    when tool-suggest discovery uses remote plugins
    - preserve existing marketplace listing behavior for all other callers
    and when remote plugins are disabled
    - add regression coverage proving the curated marketplace is excluded
    before its malformed manifest can be read
    
    ## Why
    
    Tool-suggest discovery previously loaded every local `openai-curated`
    plugin manifest and only discarded that marketplace afterward when
    remote plugins were enabled. The remote catalog is used in that mode, so
    the local scan consumed CPU without contributing discoverable plugins.
    
    ## Impact
    
    Remote-plugin tool suggestion discovery no longer reads the local
    curated marketplace and its plugin manifests. `openai-bundled`,
    configured marketplaces, normal `plugin/list` behavior, and local
    curated discovery when remote plugins are disabled are unchanged.
    
    ## Validation
    
    - `just test -p codex-core-plugins
    list_marketplaces_can_skip_openai_curated_before_loading`
    - `just test -p codex-core
    list_tool_suggest_discoverable_plugins_omits_openai_curated_when_remote_enabled`
    - `just fmt`
    - `git diff --check`
  • [codex] add /import for external agents (#27071)
    ## Why
    
    External-agent import should be discoverable and deliberate without
    blocking startup or claiming the public `codex [PROMPT]` CLI namespace.
    The slash command keeps the flow local to the interactive TUI and reuses
    the existing app-server import API.
    
    ## What changed
    
    - add the user-facing `/import` slash command
    - detect external-agent importable items only when the command is
    invoked
    - run imports through the embedded local app-server
    - show start and completion messages, refresh configuration, and block
    duplicate imports while one is pending
    - reject the flow for unsupported remote and local-daemon sessions
    
    ## Validation
    
    - `just test -p codex-tui external_agent_config_migration` (10 passed)
    - manually exercised an isolated TUI fixture with existing
    external-agent setup and session data using a fresh `CODEX_HOME`
    - verified picker customization, plugin and session detection, import
    completion, repeated invocation, and imported-session resume context
    - the broader `just test -p codex-tui` run passed 2,805 tests, with 2
    unrelated guardian feature-flag failures and 4 skipped tests
    
    ## Draft follow-ups
    
    - review whether completion messaging should remain attached to the
    initiating chat if the user switches chats during an import
    - review shutdown semantics for an in-progress background import
    
    ## Stack
    
    1. [#27064](https://github.com/openai/codex/pull/27064): remove the
    startup migration flow
    2. [#27065](https://github.com/openai/codex/pull/27065): extract the
    picker renderer
    3. [#27070](https://github.com/openai/codex/pull/27070): add the
    external-agent import picker UX
    4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
    through `/import`
    
    **This PR is stack item 4.** Draft while the lower stack dependencies
    are reviewed.
  • [codex] Move release platform rules into bazel package (#27321)
    ## Intent
    
    Keep release-specific Bazel helpers out of the shared Rust crate
    definitions and colocate them with Bazel platform configuration.
    
    ## Implementation
    
    Moves `multiplatform_binaries` and its platform list from `defs.bzl`
    into `bazel/platforms/release_binaries.bzl` and updates the CLI load
    site. Behavior is unchanged.
    
    ## Validation
    
    - `bazel query //codex-rs/cli:release_binaries`
    
    Stack: 1 of 6.
  • fix: Auto-recover from corrupted sqlite databases (#26859)
    Further investigation of the sqlite incidents showed that the problems
    are due to corruption from the older version of SQLite that we recently
    upgraded, and that the data is truly corrupted in the root database --
    recovery of all data is not possible. Given that the data is
    reconstructable from the rollouts on disk, we should just auto-backup
    the database and let codex rebuild the rollout info from the disk
    rollouts.
    
    The new behavior is that appserver auto-backs-up and rebuilds (with logs
    reflecting that behavior). The CLI now pops a message letting you know
    this happened and the paths of the backed-up corrupt db and the new
    database. There is also context added so that the desktop app can read
    the rebuild info from it and inform the user with it.
  • feat(doctor): report editor and pager environment (#27081)
    ## Background
    
    This was prompted by
    [#26858](https://github.com/openai/codex/issues/26858), where the
    attached doctor report did not include the editor selection and I had to
    [ask which editor was in
    use](https://github.com/openai/codex/issues/26858#issuecomment-4653829891)
    before investigating the external-editor newline issue. Capturing these
    variables in doctor makes that context available up front in future
    reports.
    
    `codex doctor` is intended to capture enough local context to diagnose
    startup and terminal behavior, but it did not report the environment
    variables that select an external editor or configure command pagers.
    
    The TUI [prefers `VISUAL` over
    `EDITOR`](https://github.com/openai/codex/blob/56554904babcaacf4444a2cc90716880837dff7c/codex-rs/tui/src/external_editor.rs#L31-L38),
    so missing or unexpected values can explain why the external-editor
    shortcut fails or launches the wrong command. Pager values are also
    useful inherited-shell context even though [unified exec normalizes its
    effective pager variables to
    `cat`](https://github.com/openai/codex/blob/56554904babcaacf4444a2cc90716880837dff7c/codex-rs/core/src/unified_exec/process_manager.rs#L60-L70).
    
    These variables can contain arbitrary command arguments or inline
    environment assignments. The human report is local, but `codex doctor
    --json` may be attached to feedback, so the machine-readable report
    should not include their raw contents.
    
    ## What Changed
    
    - Report `VISUAL` and `EDITOR` in the system environment details, using
    `not set` when either variable is absent.
    - Report inherited `PAGER`, `GIT_PAGER`, `GH_PAGER`, and `LESS` values
    when present.
    - Preserve full values in local human output while reducing these fields
    to `set` or `not set` in redacted JSON output.
    - Add structured check, JSON-redaction, rendered-output, and snapshot
    coverage.
    
    ## How to Test
    
    1. From `codex-rs`, run Codex with explicit editor and pager variables:
    
       ```sh
    env VISUAL='code --wait' EDITOR=vim PAGER='less -R' GIT_PAGER=delta
    GH_PAGER=less LESS=-FRX \
         cargo run -p codex-cli --bin codex -- doctor --no-color
       ```
    
    2. Confirm the `system` details show the full values for all six
    variables.
    3. Unset the pager variables and rerun the command. Confirm pager rows
    are omitted while missing editor variables are shown as `not set`.
    4. Run the same configured environment with `doctor --json`. Confirm
    each configured editor or pager field is reported as `set` and none of
    the raw commands or arguments appear in the JSON.
    
    Targeted tests:
    
    - `just test -p codex-cli` (279 tests passed)
  • Enforce configured network proxy in codex sandbox (#27035)
    ## Why
    
    `codex sandbox` can start a network proxy from a configured permission
    profile. Previously, sandbox-level containment was tied to managed
    network requirements rather than whether a proxy was actually active.
    This meant config-driven proxy policies were not consistently enforced
    as the sandbox's only network path.
    
    ## What changed
    
    - Enable proxy-only network containment whenever `codex sandbox` starts
    a network proxy.
    - Apply the same active-proxy check to the macOS and Linux sandbox
    paths.
    - Add a Linux regression test that verifies a sandboxed command cannot
    establish a direct connection while the configured proxy is active.
    
    ## Test plan
    
    - `just test -p codex-cli debug_sandbox::tests`
    - `sandbox_with_network_proxy_blocks_direct_loopback_access` runs on
    Linux to cover the config-driven proxy path end to end.
  • cli: add -P sandbox permissions profile alias (#27054)
    ## Why
    
    `codex sandbox --permissions-profile` is useful when running commands
    under a named permissions profile, but the long option is cumbersome for
    a debugging-oriented command. `-p` is already used for the config
    profile selector, so `-P` gives the permissions profile selector a
    compact, non-conflicting alias.
    
    ## What Changed
    
    - Added `short = 'P'` to the `permissions_profile` option for the macOS,
    Linux, and Windows sandbox command structs in
    [`codex-rs/cli/src/lib.rs`](https://github.com/openai/codex/blob/6d9f9c5cdcaa0a156aa2dabbde259ae5e9e8bc0b/codex-rs/cli/src/lib.rs#L29-L112).
    - Added parser coverage for `codex sandbox -P :workspace -- echo` in
    [`codex-rs/cli/src/main.rs`](https://github.com/openai/codex/blob/6d9f9c5cdcaa0a156aa2dabbde259ae5e9e8bc0b/codex-rs/cli/src/main.rs#L2883-L2896).
    
    ## Verification
    
    - `just test -p codex-cli` passed, including the new
    `sandbox_parses_permissions_profile_short_alias` parser test.
  • [plugins] Expose marketplace source in marketplace list JSON (#27009)
    ## Summary
    - Follow-up to #26417 and #26631
    - Add `marketplaceSource` to `codex plugin marketplace list --json`
    entries for configured marketplaces
    - Reuse the existing `marketplaceSource` shape from `codex plugin list
    --json`
    - Keep human-readable marketplace list output unchanged
    - Add CLI coverage for configured local and git marketplace sources
    
    Example:
    
    ```json
    {
      "marketplaces": [
        {
          "name": "debug",
          "root": "/path/to/.codex/.tmp/marketplaces/debug",
          "marketplaceSource": {
            "sourceType": "git",
            "source": "https://example.com/acme/agent-skills.git"
          }
        }
      ]
    }
    ```
    
    ## Validation
    - `just fmt`
    - `just fix -p codex-cli`
    - `just test -p codex-cli marketplace_list`
    - `just test -p codex-cli`
  • fix(tui): accept prompts with resume and fork (#26818)
    ## Why
    
    Interactive `codex resume` and `codex fork` expose both a session ID
    positional and an initial prompt positional. With `--last`, Clap still
    assigns the first positional to the session ID, so a command such as
    `codex fork --last "/compact focus on auth"` either fails parsing or
    attempts to look up the prompt as a session ID instead of sending it to
    the latest session.
    
    This makes it impossible to select the latest session and immediately
    provide a follow-up prompt, even though `codex exec resume --last`
    already supports that workflow.
    
    <img width="1746" height="1024" alt="CleanShot 2026-06-06 at 17 00
    47@2x"
    src="https://github.com/user-attachments/assets/86885c07-a23c-48ee-b0ee-47f2484f6eb7"
    />
    
    ## What Changed
    
    - Reinterpret the first positional as the initial prompt when
    interactive `resume --last` or `fork --last` is used and no explicit
    second prompt was parsed.
    - Preserve the existing `resume SESSION_ID PROMPT` and `fork SESSION_ID
    PROMPT` behavior.
    - Add parser-level regression coverage for latest-session and
    explicit-session prompt forms.
    
    ## How to Test
    
    1. Start an interactive session, exit it, then run `codex resume --last
    "continue from the latest session"`.
    2. Confirm Codex resumes the latest session and submits the supplied
    prompt instead of treating it as a session ID.
    3. Run `codex fork --last "take a different approach"`.
    4. Confirm Codex forks the latest session and submits the supplied
    prompt.
    5. Also verify `codex resume SESSION_ID "continue here"` and `codex fork
    SESSION_ID "branch here"` still target the explicit session and submit
    the prompt.
    
    Targeted tests:
    - `just test -p codex-cli` (267 passed)
  • [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.
  • Add JSON output for plugin subcommands (#26631)
    ## Summary
    - Follow-up to #25330 and #26417
    - Add `--json` output for `codex plugin add` and `codex plugin remove`
    - Add `--json` output for `codex plugin marketplace
    add/list/upgrade/remove`
    - Keep existing human-readable output unchanged
    - Keep existing error handling/stderr behavior unchanged; `--json`
    changes successful stdout output only
    - Align marketplace add/remove JSON field names with the existing
    app-server protocol shape
    - Add CLI coverage for plugin and marketplace JSON outputs
    
    ## Validation
    - `just fmt`
    - `just fix -p codex-cli`
    - `just test -p codex-cli`
  • Open Windows app workspaces via deep link (#26500)
    ## Summary
    
    Fixes #26423.
    
    On Windows, `codex app PATH` detected Codex Desktop and launched the app
    shell target, then only printed a manual instruction to open the
    workspace. The Desktop app already supports
    `codex://threads/new?path=...`, so the CLI can open the requested
    workspace directly.
    
    This updates the Windows launcher to normalize the workspace path,
    encode it into a `codex://threads/new` deep link, and open that URL when
    Codex Desktop is installed. The installer fallback still opens the
    Windows installer and prints the workspace path for after installation.
  • Expose configured marketplace source in plugin list JSON (#26417)
    ## Summary
    - Follow-up to #25330
    - Add `marketplaceSource` to `codex plugin list --json` entries for
    configured marketplaces
    - Keep the existing per-plugin `source` field unchanged; this still
    reports the local plugin source path
    - Include only the configured marketplace `sourceType` and `source` from
    `config.toml`
    - Keep human-readable output unchanged
    - Add CLI coverage for configured local and git marketplace sources
    
    Example:
    
    ```json
    {
      "source": {
        "source": "local",
        "path": "/path/to/.codex/.tmp/marketplaces/debug/plugins/sample"
      },
      "marketplaceSource": {
        "sourceType": "git",
        "source": "https://example.com/acme/agent-skills.git"
      }
    }
    ```
    
    ## Validation
    - `just fmt`
    - `just fix -p codex-cli`
    - `just test -p codex-cli plugin_list`
  • [codex] Add plugin list JSON output (#25330)
    ## Summary
    - add `--json` output to `codex plugin list` with `installed` and
    `available` arrays
    - add `--available` for JSON output only; using it without `--json` is
    rejected
    - keep the existing non-JSON table output unchanged
    - add CLI coverage for JSON installed/available output and the
    `--available`/`--json` requirement
    
    ## Validation
    - `just test -p codex-cli plugin_list`
    - `just fix -p codex-cli`
    - `git diff --check`
    
    Note: `just fmt` ran Rust formatting first, then failed in the Python
    ruff step because `openai-codex-cli-bin==0.132.0` has no wheel for this
    Linux platform.
  • Wire managed MITM CA trust into child env (#22668)
    ## Stack
    1. Parent PR: #18240 uses named MITM permissions config.
    2. This PR wires managed MITM CA trust into spawned child processes.
    
    ## Why
    When Codex terminates HTTPS for limited mode or MITM hooks, child HTTPS
    clients need to trust Codex's managed MITM CA. Exporting proxy URLs
    alone is not enough, but blindly replacing user CA settings would be
    wrong: it can break custom enterprise/test roots, leak unreadable CA
    files into generated bundles, or make the child env disagree with its
    sandbox policy.
    
    ## Summary
    1. Build immutable managed CA bundles under `$CODEX_HOME/proxy` that
    include native roots, the managed MITM CA, and only inherited or
    command-scoped CA bundles the child is allowed to read.
    2. Export curated CA env vars alongside managed proxy env vars while
    preserving user CA override semantics, including nested Codex
    `SSL_CERT_FILE` precedence.
    3. Thread generated CA bundle paths into child sandbox readable roots,
    including debug sandbox execution, so the exported env vars work inside
    sandboxed commands.
    4. Remove only Codex-generated MITM CA bundle env when a child
    intentionally drops managed proxying for escalation or no-proxy retry.
    5. Document the managed CA bundle behavior and cover env injection,
    per-child bundle generation, sandbox readable roots, and no-proxy
    cleanup in tests.
    
    ## Validation
    1. Ran `just test -p codex-network-proxy`.
    2. Ran `just test -p codex-protocol`.
    3. Ran `just fix -p codex-network-proxy -p codex-protocol`.
    4. Tried focused `codex-core` validation, but the crate currently fails
    to compile in `core/tests/suite/guardian_review.rs` because an existing
    `Op::UserInput` initializer is missing `additional_context`.
    
    ---------
    
    Co-authored-by: Eva Wong <evawong@openai.com>
  • Add reasoning-only status surface item (#25504)
    Closes #24886.
    
    ## Why
    Users can configure the TUI status line and terminal title with
    `model-with-reasoning`, but issue #24886 asks for a compact
    reasoning-only item. That lets a setup show just `default`, `low`,
    `medium`, `high`, or `xhigh` without repeating the model name.
    
    ## What changed
    - Added a `reasoning` item for `/statusline` and `/title` setup flows.
    - Rendered the item from the effective reasoning effort, including
    collaboration-mode overrides.
    - Registered `reasoning` with `codex doctor` so Codex-generated
    terminal-title config is not reported as invalid.
    - Updated TUI setup snapshots so the picker previews include the new
    item.
  • Use deep links for macOS codex app paths (#25485)
    ## Why
    
    `codex app [PATH]` is the documented CLI entry point for opening Codex
    Desktop on a workspace. Recent desktop builds can focus the app while
    failing to honor paths passed as macOS document-open arguments via `open
    -a Codex.app <workspace>`, which broke `codex app .` for users. See
    #25333; related report: #25166.
    
    The desktop app still supports the explicit
    `codex://threads/new?path=...` route, so the CLI should use that
    app-owned launch surface instead of depending on folder-open event
    delivery.
    
    ## What Changed
    
    - Build a `codex://threads/new?path=<workspace>` URL in the macOS app
    launcher.
    - Pass that URL to `open -a <Codex.app>` instead of passing the
    workspace path as a document argument.
    - Add coverage that workspace paths needing escaping round-trip through
    URL query encoding.
    
    ## Verification
    
    - `just test -p codex-cli codex_new_thread_url_encodes_workspace_path`
  • 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.
  • Add Windows sandbox provisioning setup command (#24831)
    ## Why
    
    Some Windows users do not have local admin access, so they cannot
    complete the elevated portion of the Windows sandbox setup when Codex
    first needs it. This adds an alpha provisioning path that an admin or IT
    deployment script can run ahead of time for the Codex user.
    
    The intended managed-deployment shape is:
    
    ```powershell
    codex sandbox setup --elevated --user "$env:COMPUTERNAME\Alice" --codex-home "C:\Users\Alice\.codex"
    ```
    
    `--elevated` is treated as the requested sandbox setup level, not as
    proof that the process is elevated. The Windows sandbox setup
    orchestration still checks that the caller is actually elevated before
    launching the helper without a UAC prompt.
    
    ## What changed
    
    - Added `codex sandbox setup --elevated` with explicit user selection
    via either `--current-user` or `--user ... --codex-home ...`.
    - Moved the CLI implementation into `cli/src/sandbox_setup.rs` instead
    of growing `cli/src/main.rs`.
    - Added a Windows sandbox `ProvisionOnly` helper mode that runs the
    elevation-required provisioning work without requiring a workspace cwd
    or runtime sandbox policy.
    - Reused the existing elevated helper path for creating/updating sandbox
    users, configuring firewall/WFP rules, and applying sandbox directory
    ACLs.
    - Persisted `windows.sandbox = "elevated"` into the target `CODEX_HOME`
    so the desktop app does not show the initial sandbox setup banner after
    pre-provisioning succeeds.
    
    ## Validation
    
    - `cargo fmt -p codex-windows-sandbox -p codex-core -p codex-cli`
    - `cargo test -p codex-cli sandbox_setup --target-dir
    target\sandbox-setup-check`
    - `cargo test -p codex-windows-sandbox
    payload_accepts_provision_only_mode --target-dir
    target\sandbox-setup-check`
    - `git diff --check`
    - Manual Windows alpha flow with a standard local user (`Mandi Lavida`):
    ran the new setup command from an admin shell, verified the target
    `.codex` contents, sandbox marker/secrets, ACLs, firewall rules, and
    desktop startup without the sandbox setup banner once experimental
    network proxy requirements were disabled.
    
    ## Notes
    
    This intentionally does not solve later elevated update coordination for
    IT-managed deployments. The setup command can still apply provisioning
    updates when run again, but a broader coordination/process story is out
    of scope for this alpha.
  • 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.
  • Add codex app-server --stdio alias (#24940)
    ## Summary
    - Add `--stdio` as a direct alias for `codex app-server --listen
    stdio://`.
    - Keep `--stdio` and `--listen` mutually exclusive.
    - Update the app-server README to document both forms.
  • Allow API-key auth for remote exec-server registration (#24666)
    ## Overview
    Allow remote `codex exec-server` registration to use existing API-key
    auth while restricting where those credentials can be sent.
    
    - Accept `CodexAuth::ApiKey` for the normal `--remote` registration
    path.
    - Restrict API-key remote registration to HTTPS `openai.com` and
    `openai.org` hosts and subdomains, with explicit HTTP loopback support
    for local development.
    - Disable registry registration redirects so credentials cannot be
    forwarded to an unvalidated destination.
    - Retain `--use-agent-identity-auth` as the explicit Agent Identity
    path.
    - Document remote registration using `CODEX_API_KEY`.
    
    ## Big picture
    Callers can now provide an API key directly to `exec-server`
    registration without first establishing ChatGPT login state:
    
    ```sh
    CODEX_API_KEY="$OPENAI_API_KEY" \
    codex exec-server \
      --remote "https://<host>.openai.org/api" \
      --environment-id "$ENVIRONMENT_ID"
    ```
    
    ## Validation
    - `cargo fmt --all` (`just fmt` is not installed on this host)
    - `cargo test -p codex-cli -p codex-exec-server`
  • 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.
  • 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
  • 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`.
  • Add doctor thread inventory audit (#24305)
    ## Why
    
    Users have been reporting missing sessions in the app. The app server
    thread listing is backed by the SQLite state DB, but the durable source
    of truth for a thread still exists on disk as rollout JSONL. When the
    state DB is incomplete, doctor should be able to show the mismatch
    directly instead of leaving users with a generic state health result.
    
    ## What changed
    
    This adds a `threads` doctor check that compares active and archived
    rollout files under `CODEX_HOME` with rows in the SQLite `threads`
    table. The check reports missing rollout rows, stale DB rows, archive
    flag mismatches, duplicate rollout thread IDs, duplicate DB paths,
    source/provider summaries, and bounded samples of affected rollout
    paths.
    
    It also adds a read-only state audit helper in `codex-rs/state` so
    doctor can inspect thread rows without creating, migrating, or repairing
    the database.
    
    ## Sample output
    
    ```text
      ⚠ threads      rollout files are missing from the state DB
          default model provider   openai
          rollout DB active files  3910
          rollout DB archived files 2037
          rollout DB scan errors   0
          rollout DB malformed file names 0
          rollout DB scan cap reached false
          rollout DB rows          5499
          rollout DB active rows   3462
          rollout DB archived rows 2037
          rollout DB missing active rows 448
          rollout DB missing archived rows 0
          rollout DB stale rows    0
          rollout DB archive mismatches 0
          rollout DB duplicate rollout thread ids 0
          rollout DB duplicate DB paths 0
          rollout DB model providers openai=5359, lmstudio=35, mock_provider=33, lite_llm=26, proxy=26, ollama=15, lms=4, local-usage-limit=1
          rollout DB sources       vscode=2587, cli=1494, subagent:thread_spawn=577, subagent:other=502, exec=281, subagent:memory_consolidation=46, subagent:review=9, unknown=3
          rollout DB missing active sample ~/.codex/sessions/2026/0…857e-a923c712e066.jsonl
          rollout DB missing active sample ~/.codex/sessions/2025/0…877a-766dff25c68d.jsonl
          rollout DB missing active sample ~/.codex/sessions/2025/0…a8b1-7bbadc836f6e.jsonl
          rollout DB missing active sample ~/.codex/sessions/2025/0…a218-e6197f3f62f8.jsonl
          rollout DB missing active sample ~/.codex/sessions/2025/0…9011-7e30784f9932.jsonl
    ```
  • Report app-server version in codex doctor (#24311)
    ## Why
    
    We are seeing cases where users have an old background app-server still
    running. `codex doctor` already reports background server state, but
    without the running app-server version it is harder to diagnose
    behaviors that depend on the daemon build.
    
    ## What changed
    
    - Reused the app-server daemon's passive initialize probe through a
    narrow `probe_app_server_version` helper.
    - Updated the `codex doctor` Background Server section to report
    `app-server version: <version>` when the socket is reachable.
    - Preserved the not-running OK behavior and report `app-server version:
    unavailable (<short error>)` when a socket exists but the passive probe
    fails.
  • feat(doctor): add environment diagnostics (#24261)
    ## Why
    
    Issue #23031 was hard to diagnose from existing `codex doctor` output
    because support could not see the OS language, resolved Git install, Git
    repo metadata, Windows console mode/code page, or terminal-title inputs
    that affect the TUI startup path. This adds those read-only signals to
    `codex doctor` so Windows, Linux, and macOS reports carry the context
    needed to investigate similar terminal rendering regressions.
    
    Refs #23031
    
    ## What Changed
    
    - Add a `system.environment` check for OS type/version, OS language, and
    locale env vars.
    - Add a `git.environment` check for the selected Git executable, PATH
    Git candidates, version, exec path/build options, repository root,
    branch, `.git` entry, and `core.fsmonitor`.
    - Add Windows console code page and VT-processing mode details to
    terminal diagnostics.
    - Add a `terminal.title` check for configured/default title items and
    resolved project-title source/value.
    - Surface startup warning counts in config diagnostics and teach human
    output to render the new categories.
    
    ## How to Test
    
    1. On Windows, check out this branch and run `cargo run -p codex-cli --
    doctor --summary`.
    2. Confirm the Environment section includes `system`, `git`, `terminal`,
    and `title` rows.
    3. Run `cargo run -p codex-cli -- doctor --json`.
    4. Confirm the JSON contains `system.environment`, `git.environment`,
    and `terminal.title`; on Windows, confirm `terminal.env` details include
    console code pages and `VT processing` for stdout/stderr.
    5. From a non-git directory, run the same `doctor --json` command and
    confirm the Git check reports `repo detected: false` rather than
    warning.
    
    Targeted tests:
    
    - `cargo test -p codex-cli doctor`
    - `cargo test -p codex-cli`
  • Support OAuth options in codex mcp add (#24120)
    ## Summary
    - add `--oauth-client-id` and `--oauth-resource` options for streamable
    HTTP `codex mcp add` registrations
    - persist those options in MCP server config and use them during the
    immediate OAuth login flow
    - cover add-time serialization of both OAuth options in the CLI
    integration tests
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-cli`
    - `just fix -p codex-cli`
  • cli: support --profile for codex sandbox (#24110)
    ## Why
    
    `codex sandbox` now always runs the host sandbox backend, so it should
    accept the same profile selection mechanism as the rest of the runtime
    CLI surface. Without `--profile`, sandbox debugging can exercise only
    the default config stack unless users manually translate profile config
    into ad hoc `-c` overrides.
    
    Supporting `--profile` lets sandbox invocations load
    `$CODEX_HOME/<name>.config.toml`, including permission profile
    configuration, before resolving the sandbox policy for the command being
    run.
    
    ## What Changed
    
    - Added `--profile NAME` / `-p NAME` to the host-specific `codex
    sandbox` argument structs as `config_profile`.
    - Allowed root-level `codex --profile NAME sandbox ...` and made a
    sandbox-local `codex sandbox --profile NAME ...` override the root
    selection.
    - Threaded `LoaderOverrides` through sandbox config loading so selected
    config profile files participate in permission resolution before the
    legacy read-only fallback.
    - Documented the new sandbox flag in `codex-rs/README.md`.
    
    ## Verification
    
    - Added parser coverage for `codex sandbox --profile`.
    - Added sandbox config-loader coverage that verifies selected config
    profile loader overrides select the profile config rather than falling
    back to read-only.
    - Ran `cargo test -p codex-cli`.
  • cli: infer host sandbox backend (#24102)
    ## Why
    
    `codex sandbox` previously required an OS subcommand like `linux`,
    `macos`, or `windows`, even though the command can only run the sandbox
    backend available on the current host. That made the CLI imply a
    cross-OS choice that does not exist.
    
    ## What changed
    
    - Collapse `codex sandbox <os>` into `codex sandbox [COMMAND]...` by
    wiring the `sandbox` parser directly to the host-specific backend args
    with `cfg`.
    - Keep the existing backend runners for Seatbelt, Linux sandbox, and
    Windows restricted token.
    - Rename the public Windows debug sandbox runner to
    `run_command_under_windows_sandbox` for clarity.
    - Update the Rust sandbox docs and related README references to describe
    host OS selection and avoid pointing readers at legacy `sandbox_mode`
    config.
    
    ## Arg0 compatibility
    
    The `codex-linux-sandbox` helper path is still handled before normal CLI
    parsing. `arg0_dispatch()` checks whether the executable basename is
    `codex-linux-sandbox` and directly calls
    `codex_linux_sandbox::run_main()`, so removing the `sandbox linux`
    parser branch does not affect the arg0 helper flow.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - `cargo test -p codex-arg0`
    - `just fix -p codex-cli`
  • config: remove legacy profile v1 resolution (#24051)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved user-facing
    `--profile` selection onto profile v2, and
    [#23886](https://github.com/openai/codex/pull/23886) removed the old CLI
    `config_profile` override path. Core still had a second legacy path:
    `profile = "..."` could select `[profiles.*]` values while runtime
    config was built. Keeping that resolver alive preserves the old
    precedence model and profile-carrying surfaces even though profile
    selection now points at `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Reject legacy top-level `profile = "..."` config while loading runtime
    config, with an error that points callers at `--profile <name>` and
    `<name>.config.toml` in the [core load
    path](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2524-L2531).
    - Remove the remaining profile-v1 merge points from runtime config
    resolution, including features, permissions, model/provider selection,
    web search, Windows sandbox settings, TUI settings, role reloads, and
    OSS provider lookup.
    - Drop the leftover profile override surface from
    [`ConfigOverrides`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2118-L2148)
    and from the MCP server `codex` tool schema.
    - Prune profile-precedence tests that only exercised the removed
    resolver and replace them with rejection coverage for the legacy
    selector.
    
    ## Testing
    
    - Not run in this metadata pass.
    - Added
    [`legacy_profile_selection_is_rejected`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/config_tests.rs#L7942-L7965)
    coverage for the new runtime guard.
  • mcp: surface profile migration guidance under --profile (#23890)
    ## Why
    
    `codex --profile <name> mcp ...` should reach the same profile-v2
    migration guard as runtime commands. Otherwise legacy
    `[profiles.<name>]` users see the generic command-scope rejection
    instead of the existing guidance to move settings into
    `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Allow `codex mcp` through the `--profile` subcommand gate.
    - Pass profile loader overrides into the MCP entry point only to
    validate profile-v2 migration when a profile is present.
    - Keep MCP add/remove/list/get/login/logout behavior otherwise
    unchanged; this does not add profile-scoped MCP server management.
    - Cover the legacy profile migration error for `codex --profile work mcp
    list`.
    
    ## Testing
    
    - `cargo test -p codex-cli`
  • cli: remove legacy profile v1 plumbing (#23886)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved the
    user-facing `--profile` flag onto profile v2. The shared CLI option
    layer still carried the old `config_profile` slot and several CLI
    entrypoints still copied that value into legacy config overrides.
    Leaving that path around makes the CLI surface look like it still
    selects legacy `[profiles.*]` state even though `--profile` now means
    `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Remove the legacy `config_profile` field and merge/copy path from
    [`SharedCliOptions`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/utils/cli/src/shared_options.rs#L8-L177).
    - Stop forwarding profile-v1 overrides from CLI, exec, TUI, doctor,
    debug, feature, and exec-server paths; runtime profile selection remains
    on `config_profile_v2` through
    [`loader_overrides_for_profile`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/cli/src/main.rs#L1606-L1619).
    - Resolve local OSS provider selection from the base config in exec and
    TUI now that the legacy profile argument is gone.
    
    ## Testing
    
    - Not run (cleanup-only follow-up to #23883).
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`
  • cli: rename profile v2 flag to --profile (#23883)
    ## Why
    
    Profile v2 is taking over the user-facing profile selection path, so the
    CLI no longer needs to expose the transitional `--profile-v2` spelling.
    This switches the public args surface to `--profile` before the
    remaining legacy profile plumbing is removed separately.
    
    ## What
    
    - Rebind `--profile` and `-p` to the v2 profile name argument that
    selects `$CODEX_HOME/<name>.config.toml`.
    - Stop parsing the legacy shared CLI profile argument while keeping its
    implementation path in place for follow-up cleanup.
    - Update CLI validation, profile-name parse errors, and the
    legacy-profile collision message/tests to refer to `--profile`.
    
    ## Testing
    
    - `cargo test -p codex-cli -p codex-config -p codex-protocol -p
    codex-utils-cli`
  • feat(plugins): tabulate plugin list output (#23727)
    ## Summary
    - render `codex plugin list` as one table per marketplace with the
    marketplace manifest path shown above each table
    - surface the installed plugin version in the CLI output by threading
    `installed_version` through marketplace listing state
    - narrow the system-root exemption so only known bundled/runtime
    marketplaces skip missing-manifest failures, and keep `VERSION` empty
    for cached-but-unconfigured plugins
    
    ## Rationale
    The plugin list UX was hard to scan as a flat list and did not show
    which installed version was active. This change makes the CLI output
    easier to read in the real multi-marketplace case, keeps the plugin path
    visible, fixes the Sapphire regression where bundled/runtime marketplace
    roots were blocking `plugin list`, and addresses the two review findings
    that came out of the follow-up deep review.
    
    ## Key Decisions
    - kept the CLI output grouped per marketplace instead of one global
    table so the marketplace path can live with the rows it owns
    - kept `VERSION` as the installed version, which means it is empty until
    a plugin is actually installed
    - handled the bundled/runtime regression in the CLI snapshot validation
    path rather than widening app-server protocol or changing marketplace
    loading behavior
    - narrowed the exemption to known system marketplace names plus expected
    system paths, so user-configured marketplaces under those directories
    still fail loudly
    - gated `installed_version` on actual installed state so `VERSION`
    cannot show stale cache state for `not installed` rows
    
    ## Validation
    - `just fmt`
    - Sapphire: `cargo test -p codex-cli --test plugin_cli` (`14 passed; 0
    failed`)
    - Sapphire smoke test: bundled/runtime roots still work
      - `cargo run -q -p codex-cli -- plugin add sample@debug`
      - `cargo run -q -p codex-cli -- plugin list`
    - verified the bundled/runtime-root scenario no longer errors and shows
    the expected marketplace table output
    - Sapphire smoke test: custom marketplace under bundled path still
    errors
    - verified `failed to load configured marketplace snapshot(s)` for
    `custom-marketplace`
    - Sapphire smoke test: cached-but-unconfigured plugin hides version
    - verified `sample@debug not installed` renders with an empty `VERSION`
    column
    
    ## Sample Output
    ```text
    /tmp/custom-marketplace/plugin.json
    NAME          VERSION  STATUS         DESCRIPTION
    sample@debug  1.0.0    enabled        Debug sample plugin
    other@local            not installed  Local development plugin
    ```
  • cli: add strict config to exec-server (#23719)
    ## Why
    
    PR #20559 added opt-in strict config parsing to the config-loading
    command surfaces, but `codex exec-server` was left out. That meant
    `codex exec-server --strict-config` was rejected even though the command
    can load config for remote registration, and local server startup had no
    way to fail fast on misspelled config keys.
    
    ## What Changed
    
    - Added `--strict-config` to `codex exec-server`.
    - Allowed root-level inheritance from `codex --strict-config
    exec-server`.
    - Validated config before local exec-server startup when strict mode is
    requested.
    - Reused the loaded strict-config-aware config for remote exec-server
    registration auth.
    - Added CLI coverage showing `codex exec-server --strict-config` rejects
    unknown config fields.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - New integration test:
    `strict_config_rejects_unknown_config_fields_for_exec_server`
    
    ## Documentation
    
    Any strict-config command list on developers.openai.com/codex should
    include `codex exec-server` with the other supported config-loading
    entry points.
  • Migrate exec-server remote registration to environments (#23633)
    ## Summary
    - migrate exec-server remote registration naming from executor to
    environment
    - align CLI, public Rust exports, registry error messages, and relay
    test fixtures with the environment registry contract
    - keep the live registration path and response model consistent with
    `/cloud/environment/{environment_id}/register`
    
    ## Verification
    - `cargo test -p codex-exec-server
    remote::tests::register_environment_posts_with_auth_provider_headers
    --manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
    - `cargo test -p codex-exec-server --test relay
    multiplexed_remote_environment_routes_independent_virtual_streams
    --manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
    - `cargo check -p codex-cli --manifest-path
    /Users/richardlee/code/codex/codex-rs/Cargo.toml` (still running when PR
    opened; will update after completion if needed)
  • runtime: detect Codex package layout (#23596)
    ## Why
    
    The package-builder stack now creates a canonical Codex package
    directory where the entrypoint lives under `bin/`, bundled helper
    resources live under `codex-resources/`, and bundled PATH-style tools
    live under `codex-path/`. That layout is not specific to the standalone
    installer: npm, brew, install scripts, and manually unpacked artifacts
    should all be able to use the same package shape.
    
    The Rust runtime still only knew about the legacy standalone release
    layout, where resources sit next to the executable. A packaged binary
    therefore would not identify its package root or prefer the bundled `rg`
    from `codex-path/`.
    
    ## What changed
    
    - Adds `CodexPackageLayout` to `codex-install-context` and detects it
    from an executable path shaped like `<package>/bin/<entrypoint>` when
    `<package>/codex-package.json` is present.
    - Splits `InstallContext` into an install `method` plus an optional
    package layout so the layout is shared across npm, bun, brew,
    standalone, and other launch contexts.
    - Stores package-layout paths as `AbsolutePathBuf` values.
    - Keeps `codex-resources/` and `codex-path/` optional so Codex can still
    run with degraded behavior if sidecar directories are missing.
    - Updates `InstallContext::rg_command()` to prefer bundled
    `codex-path/rg` or `rg.exe`, then fall back to the legacy standalone
    resources location, then system `rg`.
    - Updates `codex doctor` reporting so package installs show package,
    bin, resources, and path directories, and so bundled search detection
    recognizes `codex-path/` for any install method.
    
    ## Test plan
    
    - `cargo test -p codex-install-context`
    - `cargo test -p codex-cli`
    - `cargo test -p codex-tui
    update_action::tests::maps_install_context_to_update_action`
    - `just bazel-lock-check`