Report MCP OAuth Auto store drift - #30296
Conversation
f1a3e95 to
ed8c216
Compare
04aa181 to
96b3707
Compare
ed8c216 to
2e95aa0
Compare
71071e9 to
857c0dd
Compare
2e95aa0 to
51f18bc
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b4e0bd9c45
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| codex-config = { workspace = true } | ||
| codex-exec-server = { workspace = true } | ||
| codex-keyring-store = { workspace = true } | ||
| codex-otel = { workspace = true } |
There was a problem hiding this comment.
Include the Bazel lockfile update
Because this change adds new Rust dependency edges for codex-rmcp-client and updates Cargo.lock, leaving MODULE.bazel.lock out of the diff will trip the repo's Bazel lock-drift check in CI; run just bazel-lock-update from the repo root and include the resulting lockfile update with these dependency changes.
AGENTS.md reference: AGENTS.md:L37-L39
Useful? React with 👍 / 👎.
[Codex Thread 019edd6d-6f14-74e2-853c-345d1803d4a6](https://codex-thread-link.openai.chatgpt-team.site/thread/019edd6d-6f14-74e2-853c-345d1803d4a6) ## Stack Review and merge in order. Every layer is independently correct and documents its safe stopping point. 1. [#30292](#30292) — aggregate File/Secrets store locking 2. [#30293](#30293) — resolve and lifecycle-pin the exact OAuth store 3. [#30416](#30416) — serialized authoritative refresh transaction 4. [#30294](#30294) — Codex-owned transport refresh and one-shot 401 recovery 5. [#30295](#30295) — login/logout transaction serialization 6. [#30296](#30296) — diagnostic-only Auto store drift reporting **This PR is layer 1.** ## Why MCP OAuth credentials stored in File or Secrets share one aggregate map. Concurrent read-modify-write operations for different MCP servers can both read the same snapshot and let the later write discard the earlier update. That is a correctness problem independent of refresh-token rotation. ## What this PR does - Adds a bounded cross-process lock around aggregate File and Secrets loads, saves, and deletes. - Distinguishes aggregate-lock failures from Secrets backend unavailability, so Auto can fall back only for the latter and cannot bypass serialization by reading or writing File. - Keeps Direct keyring operations outside this lock because they are already per credential. - Releases the Secrets aggregate lock before legacy File cleanup so cross-store cleanup cannot create nested aggregate-lock ordering. - Tests actual contention by waiting for an observed `WouldBlock`, rather than assuming a sleeping worker reached the lock. - Tests load and save with only the Secrets lock path broken while fallback File remains readable and writable. ## Decisions and non-goals - This lock protects aggregate-store read-modify-write integrity only. It does not choose a credential authority or serialize an OAuth refresh transaction. - The lock is scoped to the active `CODEX_HOME`, matching the aggregate files it protects. - Lock waits are bounded, and coordination failures are surfaced rather than treated as evidence that Secrets is unavailable. ## Safe stopping point This PR can merge alone. It prevents lost updates and partial aggregate reads. Auto can still resolve again during a client lifecycle until layer 2, and concurrent refreshes remain possible until layer 3. ## Validation - `just test -p codex-rmcp-client` (96 passed; expected environment skips) - Focused aggregate File/Secrets lock contention and Auto fallback tests
b4e0bd9 to
b5c26c3
Compare
fc45ebb to
9930f58
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
9930f58 to
bbfee78
Compare
b5c26c3 to
16e5d3f
Compare
bbfee78 to
624e8d8
Compare
16e5d3f to
c74ec6a
Compare
624e8d8 to
3100d43
Compare
c74ec6a to
2177311
Compare
3100d43 to
77ff788
Compare
2177311 to
14578ad
Compare
| resolution_reason = reason.as_str(), | ||
| "MCP OAuth Auto storage resolved differently than its previous use" | ||
| ); | ||
| if let Some(metrics) = codex_otel::global() { |
There was a problem hiding this comment.
I think this can permanently drop the drift signal in the MCP CLI. We persist the new baseline before checking codex_otel::global(), but codex mcp list and mcp login / OAuth-enabled mcp add run without building an OtelProvider. If one of those commands first sees the transition, the counter is skipped and the next telemetry-enabled process sees no change
| ) -> Result<Option<StoreResolutionChange>> { | ||
| // This short, best-effort lock serializes only the diagnostic map. It must not make an OAuth | ||
| // operation wait behind the 60-second credential-store lock budget. | ||
| let _lock = OAuthStoreLock::acquire_in( |
There was a problem hiding this comment.
This makes the “best-effort” diagnostic part of the auth critical path
| // the comparison baseline. Only repeated Auto resolution under one backend indicates the | ||
| // availability drift this diagnostic is meant to surface. | ||
| Ok(previous.and_then(|previous| { | ||
| (previous.store_mode == OAuthCredentialsStoreMode::Auto |
There was a problem hiding this comment.
This also fires with a healthy keyring: an existing File credential loads through Ok(None), then a normal re-login saves to Keyring and removes File. That expected migration gets reported as availability drift, and without the previous reason the metric can’t distinguish it from a real backend failure
[Codex Thread 019edd6d-6f14-74e2-853c-345d1803d4a6](https://codex-thread-link.openai.chatgpt-team.site/thread/019edd6d-6f14-74e2-853c-345d1803d4a6) ## Stack Review and merge in order. Every layer is independently correct and documents its safe stopping point. 1. [#30292](#30292) — aggregate File/Secrets store locking 2. [#30293](#30293) — resolve and lifecycle-pin the exact OAuth store 3. [#30416](#30416) — serialized authoritative refresh transaction 4. [#30294](#30294) — Codex-owned transport refresh and one-shot 401 recovery 5. [#30295](#30295) — login/logout transaction serialization 6. [#30296](#30296) — diagnostic-only Auto store drift reporting **This PR is layer 2.** ## Why `Auto` is keyring-first with a File fallback, but re-evaluating that policy during transport reconstruction or persistence can make one MCP client read from one store and later write to another. With rotating refresh tokens, the second store may contain an older token. This layer makes the source selected at client startup explicit and keeps that authority stable for the client lifecycle. ## What this PR does - Keeps `resolve_oauth_tokens_from_store_policy` as the single configured-policy entry point and returns both credentials and the concrete File or Keyring source that supplied them. - Puts exact `load`, `save`, and `delete` operations on `ResolvedOAuthCredentialStore`, making “resolve configured policy” and “use the selected authority” distinct at call sites. - Pins the first concrete source in `pinned_credential_store` in the transport recipe, so initialization retries and session reconstruction cannot re-evaluate `Auto` and adopt another store. - Gives `OAuthPersistor` the resolved store and keeps subsequent persistence and removal on that authority. - Uses a typed keyring-load error to distinguish aggregate-store coordination failures from ordinary backend failures; a coordination failure is surfaced instead of triggering File fallback. - Keeps login-time `Auto` behavior unchanged: prefer Keyring, fall back to File when unavailable, and clean up legacy File state after a successful keyring save. - Adds structured server/backend context when fallback cleanup fails. ## Explicit decisions and non-goals - The selection is lifecycle-local and in memory. This PR does not add a durable backend selector, migration, reconciliation registry, or global source of truth outside `CODEX_HOME`. - `Auto` may choose File at the start of a later process if keyring availability changes. Once this client resolves, a selected-store failure is returned instead of hot-switching. - Different `CODEX_HOME` instances remain independent even when they can access the same Direct keyring credential. - Cross-process refresh serialization is intentionally not part of this layer. ## Safe stopping point This PR can merge alone. A single MCP client no longer hot-switches credential stores across transport rebuilds or persistence. Two processes can still refresh the same selected credential concurrently until layer 3. ## Review size The net layer is 9 files, +668/−144. The production change remains focused on store resolution and lifecycle pinning; the largest follow-up is integration coverage that drives real session recovery. ## Validation - `just test -p codex-rmcp-client` (99 passed; 5 expected skips) - Real-client 404 recovery coverage with different Keyring and File tokens; captured bearer headers prove the stale File token is never sent - Mutation check: removing the lifecycle pin makes that integration regression fail by observing the stale File token
* ci: route build IO through Dev Drives (#31357)
## Why
Windows Cargo and Bazel jobs spend significant time in filesystem-heavy
build and cache directories. Route those directories through one CI
build root so Windows can use its Dev Drive and Unix can use a stable
cache root.
## What
- Have `setup-ci` define `CI_BUILD_ROOT`, `CARGO_TARGET_DIR`, Bazel
cache/output paths, and temp paths.
- Require Windows to find or provision a verified Dev Drive instead of
falling back to `C:`.
- Pass the shared Bazel output base to `setup-bazel` so its explicit
`output_base` does not defeat Dev Drive routing.
- Point nextest, release, and V8 source-build paths at the shared
environment contract.
## Benchmark results
One-off cold-cache WPR/ETW traces show the explicit Bazel output-base
routing removes the dominant `C:` traffic:
| sample | `C:\_bazel` | summed `C:` traffic | traced test step |
|---|---:|---:|---:|
| shard 1 before | 62.2 GiB | 85.2 GiB | 16m22s |
| shard 1 updated | 0 | 16.5 GiB | 12m05s |
| shard 3 before | 67.2 GiB | 84.6 GiB | 16m48s |
| shard 3 updated | 0 | 13.5 GiB | 11m08s |
For a cold x64 V8 source build, the retained build-tail sample showed
`D:\cargo-target` at ~1.29 GiB while measured `C:` roots totaled ~0.45
GiB (`C:\Users` ~0.33 GiB, `C:\Program Files` ~0.06 GiB, `C:\Windows`
~0.03 GiB). The full cold build took 2h20m36s.
The Bazel timing improvement is directional because both refreshed
shards failed tests. The V8 trace is a bounded build-tail sample, not
the full build. All final samples had zero lost ETW events; VHDX traffic
was excluded from the optimization ranking.
Runs: [baseline
Bazel](https://github.com/openai/codex/actions/runs/28911908527),
[updated
Bazel](https://github.com/openai/codex/actions/runs/28917133701), [V8
build tail](https://github.com/openai/codex/actions/runs/28933626678).
## Manual validation
- Ran `just fmt`.
- Ran `just test-github-scripts` (35 tests).
- Parsed GitHub Actions YAML with `yq`.
- Ran `git diff --check`.
## Stack
- [#31332](https://github.com/openai/codex/pull/31332) — parameterize
Cargo target paths
- [#31356](https://github.com/openai/codex/pull/31356) — Windows 2025
runner bump
- [#31357](https://github.com/openai/codex/pull/31357) — Dev Drive I/O
routing
* chore: remove inert cargo audit workflow (#31461)
## Why
`codex-rs/.github/workflows/cargo-audit.yml` is nested below the
repository root, so GitHub Actions never discovers or runs it. RustSec
advisory enforcement already runs through the root `cargo-deny`
workflow.
## What
Remove the inert nested Cargo audit workflow.
## Validation
- Ran `git diff --check`.
- Verified the workflow is absent from GitHub registered workflows and
that root blocking CI invokes `cargo-deny`.
* test: migrate app-server v2 starts to auto env (#31614)
## Why
We should be running as many integrations tests as possible against the
split cross-OS configuration.
## What
- migrate eligible thread starts and builders to auto env
- keep explicit custom/local-executor cases local with rationale
comments
- keep auto-env coverage where possible and add narrow `TODO(anp)` skips
for fixtures that are not target-native yet
* [codex] increase tool schema compaction threshold (#31497)
## Why
The 4,000-byte limit is compacting the tool schemas of some hero
usecases.
## What changed
Raise the limit to 5,000 bytes and update compaction test fixtures
accordingly.
* code-mode: make all approvals trigger elicitation pause (#31650)
### summary
We want to pause code-mode from yielding back to the model when a
subcommand triggers an approval prompt. This means that all of these
previously inline blocking requests should also take out a
ElicitationService registration.
This also does some plumbing refactoring to request patch approval to
make it match the other `request_*_approval` methods in that it blocks
on the approval in the function instead of returning the oneshot
channel, this affords our ability to encapsulate the ElicitationService
registration via RAII.
Adds tests to confirm the blocking behavior for code_mode both in suite
tests and that the session holds them.
* Clarify device-code phishing warning (#31648)
## Why
The existing device-code warning does not help users distinguish a login
they initiated from a phishing attempt. The warning should tell users to
stop when the code came from a website or another person.
## What changed
- Updated the warning in the direct CLI and TUI device-code login flows
with actionable guidance.
- Added focused coverage for the styled direct CLI prompt.
* test(app-server): use native rollout fixture paths (#31663)
## Why
Windows CI now places temporary and build files on the `D:` Dev Drive.
Fake rollout metadata still stored `/` as its working directory, but `/`
is drive-relative on Windows. When the migrated auto-environment tests
resumed or listed those rollouts, the fixture resolved to `D:\` while
the established test expectation remained `C:\`, causing unrelated PRs
to fail the Windows app-server shard.
This follows the interaction between #31357, which moved CI build paths
to the Dev Drive, and #31614, which migrated these app-server tests to
automatic environments.
## What
- Construct fake rollout working directories with `test_path_buf("/")`,
producing a fully qualified native path on Windows while preserving `/`
on Unix.
- Use the same native test-path helper for the legacy
conversation-summary expectation.
## How to Test
Automated tests were intentionally not run locally at request; the
app-server suite was stopped during compilation. `just fmt` completed
successfully.
To verify the regression on a Windows runner:
1. Configure `TEMP` and `TMP` on a non-`C:` drive, as CI does with the
Dev Drive.
2. Run `just test -p codex-app-server`.
3. Confirm the existing thread list, read, and resume tests no longer
report `D:\` actual versus `C:\` expected paths.
This is a test-fixture-only change, so there is no product smoke path.
* [codex-apps] Omit internal fields from file payloads (#31330)
## Summary
Codex Apps file parameters are exposed to the model as local paths,
uploaded at execution time, and rewritten into provided-file payloads
before the MCP tool call.
The rewrite currently forwards two internal upload fields, `uri` and
`file_size_bytes`, even though they are not part of the documented app
file-reference shape. Strict app schemas can reject those extra fields
before execution.
## Changes
- Stop copying `uri` and `file_size_bytes` into app-facing MCP
arguments.
- Keep the internal `UploadedOpenAiFile` result unchanged.
- Preserve the existing `download_url`, `file_id`, `mime_type`, and
`file_name` behavior for scalar and array file inputs.
- Verify the MCP invocation and post-tool hook receive exactly the
documented four-field payload against an `additionalProperties: false`
schema.
This intentionally does not add schema inspection or change how
`openai/fileParams` names are discovered.
## Validation
- `just test -p codex-core mcp_openai_file` (6 passed)
- `just test -p codex-core codex_apps_file_params_` (2 passed)
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`
* Update auto review prompting (#31480)
## Why
Auto-review performance is weaker because of confusing instructions
about sandbox permissions, and because it is given many tools which are
irrelevant to it.
## What
* Update the auto review prompt
* Remove the permissions_instructions developer message
* Only pass exec_tool and view_image tool to the reviewer
## Validation
`just fmt`
`cargo test -p codex-core --lib --quiet`
* Update models.json (#21818)
Automated update of models.json.
---------
Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
Co-authored-by: Ahmed Ibrahim <aibrahim@openai.com>
Co-authored-by: Sayan Sisodiya <sayan@openai.com>
* test: add delayed exec-server transport (#31427)
## Why
Macrobenchmarks benefit from having a way to exercise remote-executor
latency without depending on Docker.
This is a very minimal first cut, if we find that simulating network
conditions is useful we can always expand this scope or switch to a more
robust network shaping approach.
## What
- add a package-local exec-server binary for Cargo and Bazel test
fixtures
- add a host-local WebSocket exec-server fixture and fixed-delay
interposer
- let TestAppServer route its auto environment through that delayed
WebSocket transport
- cover the delayed thread/start path through the public app-server API
## Stack
1. [#31425 test: add TestAppServer
builder](https://github.com/openai/codex/pull/31425)
2. [#31427 test: add delayed exec-server
transport](https://github.com/openai/codex/pull/31427)
3. [#31295 bench: add cold skill load
macrobenchmark](https://github.com/openai/codex/pull/31295)
4. [#31428 bench: add e2e benchmark
entrypoints](https://github.com/openai/codex/pull/31428)
5. [#31429 ci: smoke Bazel e2e
benchmarks](https://github.com/openai/codex/pull/31429)
* [codex] Preserve reviewer when resuming threads (#30278)
## Why
A thread resumed without an explicit reviewer could pick up the reviewer
from the current config instead of preserving the reviewer already in
use by the thread. After an app restart, this meant a thread running
with auto review could silently switch back to user review, and the next
turn could continue under the wrong reviewer.
## What changed
Persist the effective reviewer with each turn and restore the latest
persisted value when the thread resumes. If the resume request
explicitly provides a reviewer, that value still takes precedence.
## Test plan
- Added a regression test that starts a thread with auto review, records
a turn, restarts with user review in config, resumes without an
override, and verifies that auto review is preserved.
- `just test -p codex-protocol`
- `just test -p codex-state`
- `just test -p codex-rollout`
- `just test -p codex-app-server
thread_resume_preserves_persisted_approvals_reviewer`
- Clippy for the affected crates
* Expand agent core ownership (#31675)
The agent core team owns the core agent implementation and should review
changes to its adjacent runtime crates. Those crates were not covered by
the existing CODEOWNERS rules.
This adds `@openai/codex-core-agent-team` ownership for:
- `codex-rs/arg0`
- `codex-rs/codex-mcp`
- `codex-rs/exec-server`
## Validation
- `git diff --check`
No runtime tests are applicable because this only changes CODEOWNERS
metadata.
* [connectors] Refresh codex_apps /ps/mcp auth (#31486)
[Codex Thread
019f2408-dc59-79f2-b245-4c11debd1a61](https://codex-thread-link.openai.chatgpt-team.site/thread/019f2408-dc59-79f2-b245-4c11debd1a61)
## Why
Long-lived Codex sessions can outlive the ChatGPT bearer token that was
present when the MCP runtime started.
The Responses path already recovers from token expiration by refreshing
or reloading the shared `AuthManager`. The reserved `codex_apps`
hosted-plugin client did not observe that update: `McpConnectionManager`
built its `/ps/mcp` HTTP auth once from a `CodexAuth` snapshot, and
`auth_provider_from_auth` copied that snapshot bearer into a static
`BearerAuthProvider`.
After the copied bearer expired, `/ps/mcp` kept sending it even though
Responses had a newer token in the same `AuthManager`. The failure
occurred before downstream connector execution, so unrelated apps such
as Gmail, Slack, and Google Calendar could all fail with the same
transport-level `401 token_expired`.
This replaces
[openai/codex#29474](https://github.com/openai/codex/pull/29474), which
was closed for inactivity without being merged. A new long-lived-session
report reproduced the same simultaneous `/ps/mcp` expiry pattern across
unrelated apps.
## What changed
- Add an `AuthManager`-backed request-header provider in
`codex-model-provider`. It keeps an `Arc<AuthManager>` and reads
`auth_cached()` for each outbound request, so the next `/ps/mcp` call
sees a token refreshed by the existing Responses/auth-recovery flow.
- Scope that provider to the startup account, ChatGPT user, and
workspace identity. Same-identity token reloads are followed; an account
switch emits no ambient auth until account-scoped MCP state is rebuilt.
- Have `McpConnectionManager` construct the dynamic provider only for
the reserved `codex_apps` registration used by the hosted-plugin
`/ps/mcp` path.
| MCP path | Auth behavior after this change |
| --- | --- |
| Reserved `codex_apps` hosted-plugin `/ps/mcp` | Read current
same-identity auth from the shared `AuthManager` per request |
| `codex_apps` with `CODEX_CONNECTORS_TOKEN` | Keep the environment
bearer-token override |
| User-configured/direct MCP registrations | Keep their existing
configured auth path |
## Non-goals
- No plugin-service changes.
- No downstream Slack, Gmail, Calendar, or other connector
OAuth/link-refresh changes.
- No auth UI changes.
- No behavior change for user-configured/direct MCP registrations.
- No new `/ps/mcp`-initiated token refresh; this makes `/ps/mcp` observe
refreshes already performed through the shared `AuthManager`.
## Tests
- `just test -p codex-model-provider`
- Covers same-identity token reloads and refuses a changed startup
identity.
- `just test -p codex-mcp`
- `just test -p codex-core mcp_auth_refresh`
- Creates the reserved hosted-plugin `codex_apps` `/ps/mcp` client
before the shared `AuthManager` changes, updates that same manager
through its public external-auth path, performs a real `tools/call`, and
asserts the request uses the current bearer.
* model-provider: route model discovery through HTTP client factory (#31361)
* feat(rollout): persist TurnItems for paginated thread rollouts (#30188)
## Description
This PR makes new threads with `history_mode = "paginated"` persist
`ItemCompleted(item: <turn_item>)` in their rollout JSONL file.
Legacy threads keep persisting the existing legacy events. Because the
format is selected per thread, a rollout is either legacy or paginated;
we do not need to support mixed rollouts containing both
representations.
This PR depends on [#31473](https://github.com/openai/codex/pull/31473).
## Why
Paginated thread history needs stable turn/item IDs and completed item
snapshots so the later SQLite projector can materialize appended rollout
JSONL without rebuilding the whole thread.
Keeping the legacy persistence policy unchanged avoids changing
historical rollouts or the readers that still consume them.
## What changed
- Made rollout filtering history-mode aware. Paginated threads keep
completed canonical `ItemCompleted` events and drop their redundant
legacy projections; legacy threads keep the existing event set.
- Made forks inherit the source thread history mode, so copied legacy
history is never filtered as paginated.
- Made paginated threads assign IDs to locally-created response items
even when `Feature::ItemIds` is off, and reject streamed output items
that arrive without server IDs.
- Updated legacy turn replay, rollout list/search, and SQLite metadata
extraction to understand completed canonical user-message items.
* Use the image generation extension by default (#31596)
* Update models.json (#31684)
Automated update of models.json.
---------
Co-authored-by: sayan-oai <244841968+sayan-oai@users.noreply.github.com>
Co-authored-by: sayan-oai <sayan@openai.com>
* fix: parse compact release metadata in installer (#31667)
# Summary
GitHub's latest-release endpoint can return compact, single-line JSON.
The standalone installer treated release metadata as line-oriented text,
so those responses could make asset lookup fail even though the
requested assets were present.
The regression was introduced by
[#31056](https://github.com/openai/codex/pull/31056). That change reused
the `/releases/latest` metadata response for both version resolution and
asset lookup, exposing the existing formatting-sensitive asset parser to
compact responses from that endpoint.
This change parses the release metadata once with a one-pass POSIX awk
scanner. The scanner tracks JSON strings and nesting, extracts the root
release tag plus direct asset name/digest pairs, and produces the same
result regardless of whitespace or object field order. It uses POSIX
`fold` to bound awk record sizes so compact responses stay fast across
awk implementations.
Fixes #31520.
## Changes
- replace line-oriented release metadata matching with structure-aware
parsing
- reuse the parsed metadata for latest-version and asset-digest lookup
- add regression coverage for compact JSON, reordered fields, nested
decoys, and JSON-looking release text
## Design decisions
- Keep the installer dependency-free by using standard POSIX tools
already required by the shell installer.
- Parse only the GitHub release fields the installer consumes, in one
pass, instead of vendoring a general JSON library.
- Preserve asset-object boundaries so nested or string-encoded `name`
and `digest` fields cannot be mistaken for release assets.
## Testing
- Tests: focused installer suite locally and on Linux.
- Smoke tests: real pretty and compact GitHub release metadata,
latest-release resolution, and checksum-asset selection.
- Portability: macOS awk plus Linux gawk, mawk, and nawk.
- Stress coverage: randomized formatting and field order, adversarial
nested/string content, and a synthetic 2,000-asset compact response.
* core: route realtime and memories through HTTP client factory (#31362)
## Why
`ModelClient` already carries the `HttpClientFactory` resolved from
session configuration, but realtime call creation and memory
summarization still constructed the legacy default client directly.
Consequently, those first-party API requests could ignore
`features.respect_system_proxy` even when Responses traffic honored it.
These are the final direct default-client constructions in
`core/src/client.rs`, so they form one small migration unit on top of
#31361.
## What changed
- Generalize `build_responses_transport` to `build_api_transport`.
- Route realtime call creation through the helper using the selected
provider and `/realtime/calls` destination.
- Route `/memories/trace_summarize` through the same helper.
- Remove the now-unused direct `build_reqwest_client` import.
## Review guide
1. The helper rename at the bottom of `core/src/client.rs` is mechanical
and keeps existing Responses behavior unchanged.
2. The realtime call path computes the route from the final provider,
including `api_provider_override`.
3. The memories path supplies its existing endpoint to the same API
route class.
## Validation
- `cargo check --tests -p codex-core`
- `just fix -p codex-core`
## Follow-up
Direct HTTP clients outside `ModelClient` remain separate migration
slices.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/31362).
* #31637
* #31431
* #31363
* __->__ #31362
* perf(skills): reuse walk inventory for host loading (#31566)
## Why
CCA thread startup loads repository skills through the primary remote
environment. This still used the older host skill loader, which
recursively issued directory and metadata requests over the exec-server
connection. On a high-latency connection, one skill scan could turn into
hundreds of round trips.
Executor-selected skill roots already avoid this pattern: they use one
bounded filesystem walk, reuse the returned inventory, and overlap
independent reads.
## What changed
- Extract the URI-native walk inventory into a shared private discovery
module.
- Use that discovery path for both environment-owned and host/repository
skill roots.
- Reuse the walk inventory to avoid per-skill metadata existence checks
when the inventory is complete.
- Canonicalize host skill identities and parse skill files with bounded
concurrency.
- Prune hidden host directories during traversal, preserving visible
aliases and walk limits.
- Cache namespace probes by ancestor and keep all remote namespace reads
under the same concurrency bound.
- Keep safe metadata probes for incomplete walks, file symlinks, and
case aliases.
## Dependency
#31570 is now merged. It provides the optional `pruneHiddenDirectories`
walk flag used here. That flag defaults to false and is omitted when
disabled.
No other exec-server API changes are introduced.
* Bound exec-server process event reordering (#31576)
## Why
A malicious exec-server can send out-of-order process notifications
faster than missing events arrive. The client retained every future
event in a per-session reorder map, so many tiny events or a few large
output chunks could grow orchestrator memory without bound.
## What changed
- cap each session's pending process-event reorder state at 256 events
and 1 MiB
- reject individual process events larger than 1 MiB
- release byte accounting as events publish or a session fails
- fail and detach only the affected process session when a limit is
exceeded
- let the next expected event drain a full buffer, so the limits apply
to retained future state
- apply the same bounded insertion path during reconnect recovery while
safely handling dense tail output, missing exit events, newer live
notifications, and conflicting `Closed` sequences
Sequence distance is intentionally not capped because it does not affect
allocation; the event-count and byte limits are the resource-exhaustion
boundary.
## Tests
Focused exec-server tests cover oversized output, full count/byte
buffers, gap-closing delivery, dense recovery interleaved with newer
notifications, missing exit reconstruction, and conflicting terminal
sequences.
## Scope
This is limited to per-session process-event reorder state. It does not
introduce a scheduler, quota framework, or new public configuration.
* test(skills): assert symlinked metadata loading (#31756)
## Why
The host walk-inventory test writes policy metadata for a symlinked
skill, but only asserted the loaded names. It could still pass if
metadata loading regressed.
## What changed
- Compare the complete loaded skill metadata.
- Verify the first skill keeps `allow_implicit_invocation: false` while
the sibling has no policy.
Stacked on #31566.
* Resolve selected capability roots without starting executors (#31581)
## Why
A thread can select skill roots that live in an executor environment.
`skills/list` needs a passive snapshot of the roots that are usable now:
it must not start an executor, wait for recovery, or reconnect a failed
environment.
The initial implementation checked the immutable first startup result.
After a successful connection later entered recovery or failed, that
result still looked successful. A read-only catalog request could then
wait for recovery or trigger a new connection while reading the
filesystem.
## What
- inspect readiness from the current exec-server connection state
- return roots only while their environment can serve a request
immediately
- omit environments that have not started, are connecting, or are
recovering
- return warnings for missing environments and terminal connection
failures
- add a fail-fast filesystem view that never starts, waits for, or
reconnects an environment
- expose the passive selected-root snapshot through `CodexThread`
## Behavior
- Local and currently connected environments are ready.
- Starting and recovering environments are omitted without a warning so
callers can retry later.
- Missing and terminally failed environments are omitted with a warning.
- A disconnect between readiness inspection and filesystem access fails
promptly instead of crossing into the normal recovery path.
- Normal model-turn and execution paths keep their existing reconnect
behavior.
## Design
The recovery policy is private to the exec-server client. Callers choose
the explicit fail-fast filesystem method; the existing client and
filesystem APIs remain reconnecting. This keeps the passive contract at
the transport boundary instead of plumbing timeout or retry flags
through the skills stack.
## Coverage
- a lazy stdio environment stays unstarted during passive inspection
- missing and terminally failed environments surface warnings
- a real websocket disconnect proves current readiness drops, a
previously acquired fail-fast filesystem handle returns promptly, and
readiness returns after recovery
## Scope
This PR only provides passive readiness and fail-fast filesystem
primitives. It does not add app-server API fields or notifications.
## Stack
- #31582 uses these primitives for experimental thread-scoped
`skills/list`.
- #30228 adds targeted invalidation notifications.
* Stop persisting RMCP service traces (#31789)
## Why
The SQLite feedback log enables TRACE by default. `rmcp::service`
renders complete MCP service events at TRACE, including large tool
catalogs and other protocol payloads. In a busy local sample, this was
the largest retained source and individual rows reached about 2 MiB.
These rows can quickly consume the per-thread retention budget, displace
smaller diagnostics, and add unnecessary SQLite insert-and-prune work.
## What changed
- Persist `rmcp::service` at INFO and above in the SQLite feedback log.
- Keep service initialization, shutdown, warnings, and errors available
for feedback.
- Leave other tracing and OpenTelemetry subscribers unchanged.
This is intentionally target-specific. Broader persistent-log policy
changes can remain separate.
Related to #28224.
* Summarize streamed response item logs (#31792)
## Why
`handle_non_tool_response_item` logged the complete decoded
`ResponseItem` at DEBUG. Those values can contain assistant text,
reasoning content, and tool payloads that are already stored in the
durable rollout, making the SQLite copy both redundant and potentially
large or sensitive.
## What changed
- Replace the complete item dump with bounded `item_type` and `item_id`
fields.
- Keep an event-flow breadcrumb for debugging without duplicating item
content.
- Leave rollout persistence and tool-call telemetry unchanged.
The item-type match is exhaustive so new response item variants must
choose an explicit log label.
Related to #28224.
* Filter routine Hyper logs from SQLite (#31791)
## Why
The SQLite feedback log currently persists Hyper's TRACE, DEBUG, and
INFO connection-pool bookkeeping. Healthy connects, checkouts, reuse,
and timer activity generated thousands of small rows during active
sessions without adding useful feedback context.
## What changed
- Set the `hyper_util` target prefix to WARN for the SQLite feedback-log
layer.
- Continue persisting Hyper warnings and errors.
- Leave other tracing and OpenTelemetry subscribers unchanged.
This removes routine dependency chatter while preserving actionable
failures.
Related to #28224.
* Reduce MCP tool-list trace volume (#31790)
## Why
Every MCP tool-list build emitted two normal-path TRACE events per
configured server: one before waiting for tools and one after listing
them. On active sessions this produced thousands of nearly identical
SQLite rows while carrying little information beyond server readiness
and tool counts.
## What changed
- Remove the two normal-path per-server TRACE events.
- Keep the existing per-server trace span for timing and remote trace
context.
- Emit per-server details only when a server's tools are unavailable.
- Emit one bounded summary per tool-list build with available server,
unavailable server, and tool counts.
Successful builds retain the useful aggregate signal without repeating
it for every server.
Related to #28224.
* Stabilize the memories feature flag (#31804)
Memories is graduating from the experimental lifecycle, but it should
remain opt-in.
This changes the feature stage to stable while keeping `default_enabled`
false. It removes memories from the experimental menu and announcement
without enabling it by default.
Companion Codex Apps PR: https://github.com/openai/openai/pull/1110434
* fix(mcp): default Apps product SKU to codex (#31803)
## Motivation
Host-owned Apps MCP requests currently omit `X-OpenAI-Product-Sku`
unless `apps_mcp_product_sku` is explicitly configured. Codex requests
should identify themselves with the `codex` product SKU by default.
## Changes
- Default `X-OpenAI-Product-Sku` to `codex`.
- Preserve explicitly configured SKU values.
- Add coverage for both default and configured behavior.
## Impact
Codex Apps MCP requests now include the expected product identity
without requiring local configuration. Existing explicit SKU overrides
are unchanged.
## Testing
- `just test -p codex-mcp
codex_apps_server_config_sets_product_sku_header`
- `just fix -p codex-mcp`
- `just fmt`
* code-mode: retain shared MCP types for deferred tools (#31745)
## Why
When MCP tools are deferred behind `tool_search`, Code mode keeps them
callable but omits their individual declarations from the initial `exec`
description. The shared MCP `CallToolResult` types were derived only
from directly rendered tools, so deferring every MCP tool also removed
the common response contract that models need to interpret MCP results.
This restores that contract without undoing the context savings from
deferred tool definitions. This is a follow-up to #29486.
## What changed
- Track deferred Code-mode tool definitions separately from directly
rendered definitions.
- Render the shared MCP type preamble when either direct or deferred MCP
tools are available.
- Keep deferred tool declarations out of the initial prompt.
- Add unit and integration coverage for deferred MCP tools.
## Testing
- `just test -p codex-code-mode-protocol`
- `just test -p codex-core
code_mode_only_guides_all_tools_search_and_calls_deferred_app_tools`
* Import enabled plugins from known marketplaces (#31672)
## Summary
- discover enabled-plugin marketplace sources from the user-level known
marketplace registry
- preserve scoped settings and supported Git/directory declarations,
falling back to the matching registry `installLocation` for file, URL,
npm-hosted, and inline marketplace sources
- resolve relative registry materialization paths against the
external-agent home
- reuse the existing marketplace add and plugin install flow during
import
- emit `plugin_not_found` tracing and analytics with plugin and
marketplace identity
## Why
Enabled plugins can remain in settings after their marketplace source
has moved into the user-level registry. Import detection previously
skipped those plugins because it only consulted settings-defined
marketplace sources.
## Impact
`/import` can now restore those marketplaces and install their enabled
plugins through the normal plugin installer, including npm, Git, and
local plugin sources. Missing plugins remain visible in import results
and the plugin-install failure analytics stream.
* fix(tui): hide empty reasoning summaries (#31652)
## Why
Reasoning summaries can contain an empty HTML comment placeholder, `<!--
-->`, when a generated summary part has no prose. The TUI treated that
placeholder as visible summary content, so it leaked into both the
completed conversation history and persisted transcript. Multi-part
empty summaries could also leave later generated status headings visible
as orphaned transcript content.
## What
- Strip the empty reasoning-summary placeholder at the shared
`ReasoningSummaryCell` boundary.
- Suppress the cell when removing placeholders leaves only generated
bold status headings.
- Cover the multi-part placeholder-only shape with a rendering
regression test for both normal history and transcript output.
## How to Test
1. Start Codex TUI with reasoning summaries visible, for example with
`hide_agent_reasoning = false` and `model_reasoning_summary =
"detailed"`.
2. Run a turn that receives an empty generated reasoning-summary part.
3. Confirm that the in-progress status heading may appear while the turn
is running, but the completed history and transcript contain neither
literal `<!-- -->` tokens nor orphaned reasoning headings.
4. Confirm that a reasoning summary with actual prose still renders
normally.
The model output is nondeterministic, so the targeted regression covers
the exact multi-part placeholder shape from the report:
- `just test -p codex-tui
reasoning_summary_block_hides_empty_html_comment_parts`
* Remove the network proxy config wrapper (#31767)
## Why
`NetworkProxyConfig` only wrapped `NetworkProxySettings` in a single
`network` field. That extra level made runtime callers repeat `.network`
everywhere without representing a real boundary.
## What changed
- move the managed-network fields directly onto `NetworkProxyConfig`
- collapse the matching partial-config wrapper
- update runtime callers and tests to use the direct fields
- keep the user-facing permissions/profile TOML layout unchanged
The internal serialized shape now matches the runtime type itself. This
does not change managed-network behavior or the `config.toml` shape.
* fix: forward originator to Codex Apps MCP (#31481)
## Summary
- Forward Codexs canonical `originator` header on ChatGPT-hosted Apps
and plugin-runtime MCP requests.
- Preserve the configured `X-OpenAI-Product-Sku` header.
- Cover originator-only and originator-plus-SKU configurations.
## Why
Sites project creation is logged downstream of Apps MCP. Production
validation found `CODEX_UNKNOWN_DEFAULT` project-created threads that
matched `codex_surface=desktop_app` and `originator=Codex Desktop` in
`fact_codex_cli`
([query](https://kepler.gateway.data-1.internal.api.openai.org/permalink/H_mVoVPqLQ0)).
The hosted Apps MCP configuration forwarded the product SKU but not
Codexs canonical originator, so codex-backend could not derive
`CODEX_DESKTOP_APP` for those tool calls.
## Validation
- `just fmt`
- `CARGO_HOME=/private/tmp/codex-cargo-home
CARGO_TARGET_DIR=/private/tmp/codex-target just test -p codex-mcp` (106
passed)
- `CARGO_HOME=/private/tmp/codex-cargo-home
CARGO_TARGET_DIR=/private/tmp/codex-target just fix -p codex-mcp`
* codex-api: route file uploads through HTTP client factory (#31363)
## Why
Codex Apps file parameters use a three-step upload flow: create a file
record, PUT bytes to a returned signed URL, and finalize the upload.
Each step still constructed a default `reqwest` client, so the flow
could bypass `features.respect_system_proxy` even after model API
requests honored it.
This stack entry makes the resolved client policy a required input to
the upload API and resolves each concrete destination independently.
## What changed
- Require `HttpClientFactory` in `upload_openai_file`.
- Build clients for the create, signed upload, and finalize URLs through
the shared API route policy.
- Pass the factory derived from the turn configuration at the Apps/MCP
call site.
- Return a destination-aware `ClientBuild` error when enabled route
selection cannot construct a client.
- Preserve the legacy logged fallback for the feature-off
`ReqwestDefault` policy.
## Review guide
1. `codex-api/src/files.rs` changes the upload API and centralizes
route-aware client construction.
2. The three request stages each supply their actual URL, including the
separately hosted signed blob URL.
3. `core/src/mcp_openai_file.rs` is the only production caller and
supplies the turn configuration factory.
## Validation
- `cargo check --tests -p codex-api -p codex-core`
- `just test -p codex-api files` (1 matching upload test passed; 135
tests skipped by filter)
- `just fix -p codex-api -p codex-core`
## Follow-up
Other direct HTTP clients remain separate migration slices.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/31363).
* #31637
* #31431
* __->__ #31363
* tui: update safety buffering copy (#31813)
## Summary
The safety-buffering message currently leads with additional safety
checks, which can make the delay feel accusatory. Update it to describe
the system taking more time before responding, matching the revised
user-facing language.
- Replace the safety-buffering copy in both retry and non-retry flows.
- Keep the faster-model escape hatch while aligning its wording with the
updated message.
- Update the TUI snapshots for both states.
* fix(sandboxing): initialize network proxy config inline (#31830)
## Why
PR #31767 flattened `NetworkProxyConfig`, leaving the [seatbelt test
setup](https://github.com/openai/codex/blob/d72d669ca727a26765315ffe03db3dd2594d9b91/codex-rs/sandboxing/src/seatbelt_tests.rs#L660-L662)
to assign fields directly after `NetworkProxyConfig::default()`. Rust
1.95 flags that pattern as `clippy::field_reassign_with_default`, and
the macOS Bazel Clippy job treats the warning as an error, blocking
`main` and PRs based on it.
## What Changed
Initialize `enabled` and `mode` in the `NetworkProxyConfig` struct
literal, then apply the Unix socket allowlist through the existing
setter. This preserves the test behavior while satisfying Clippy.
## How to Test
This is a test-only initialization change, so there is no manual product
flow.
Targeted tests:
- `just test -p codex-sandboxing` (65 passed)
- `just clippy -p codex-sandboxing`
The local argument-comment lint could not complete because Bazel's LLVM
repository is missing the `compiler-rt` BUILD package. The touched Rust
diff was manually audited and adds no positional literal calls.
* build: ratchet direct reqwest dependencies (#31431)
## Why
The `codex-http-client` migration now has a shared implementation and
several migrated request paths, but nothing prevents a new crate from
adding another direct `reqwest` dependency while the remaining call
sites are being converted. The dependency graph should both enforce the
direction of travel and make the remaining scope visible.
This PR adds that ratchet on top of #31363. It does not claim the
migration is complete: the allowlist deliberately records all 18
first-party crates that still depend on `reqwest` directly.
## What changed
- Ban `reqwest` with cargo-deny unless its immediate parent is an
explicitly listed wrapper.
- Identify `codex-http-client` as the intended owner.
- Record the 18 current first-party direct dependents as temporary
migration exceptions.
- Separately allow six third-party integrations that own their `reqwest`
dependency: `oauth2`, `opentelemetry-http`, `opentelemetry-otlp`,
`rmcp`, `sentry`, and `webrtc-sys-build`.
- Cover both `reqwest` 0.12 and 0.13 with the same package-level rule.
## Migration rule
A new first-party crate cannot add `reqwest`. When a listed crate
finishes migrating, its direct Cargo dependency and its wrapper entry
should be removed in the same PR, so the first-party list can only
shrink.
## Review guide
The entire change is the new `reqwest` entry in `codex-rs/deny.toml`:
1. `codex-http-client` is the permanent intended wrapper.
2. The next 18 entries are the first-party migration backlog.
3. The final six entries are separately documented third-party parents
required by cargo-deny graph semantics.
## Validation
- `cargo deny check bans --hide-inclusion-graph` (`bans ok`; existing
duplicate-version warnings remain warnings)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/31431).
* #31837
* #31828
* #31825
* #31821
* #31637
* __->__ #31431
* code-mode: fix installation on darwin (#31876)
Installer needs to symlink code-mode-host next to codex on install.
* Update bundled OpenAI Docs skill for GPT-5.6 (#31842)
## Summary
Updates Codex's bundled OpenAI Docs skill for GPT-5.6.
- resolves latest/current/default-model requests through the
machine-readable `latest-model.md` contract
- fetches the exact prompting and migration guide URLs returned by the
live docs
- preserves explicitly named GPT-5 model targets
- adds bundled GPT-5.6 Sol, Terra, and Luna migration judgment
- adds a POSIX resolver wrapper plus a CommonJS entry point for Windows
- skips incompatible Node.js runtimes before trying Codex's
bundled/system fallbacks
## Developer impact
The bundled system skill follows the live GPT-5.6 docs contract and can
resolve current guidance after installation on POSIX and Windows
environments.
## Validation
- skill validator: passed
- live resolver smoke test returned `gpt-5.6-sol` and both standalone
guide URLs
- CommonJS and POSIX shell syntax checks: passed
- non-executable installed-wrapper invocation through `sh`: passed
- incompatible `$NODE` fallback to the bundled Node.js runtime: passed
- `git diff --check`: passed
- migration eval: 30/30 passed, 0 critical failures
- latest-model/prompting routing eval: 33/33 passed, 0 critical failures
`just fmt` was not run because `just` is unavailable in this
environment; the change only touches bundled Markdown and
JavaScript/shell assets.
* login: route raw auth flows through HTTP client (#31637)
## Why
Login already honors `respect_system_proxy`, but several login-owned
auth flows still construct and pass around raw `reqwest::Client` values.
That keeps those request paths coupled to the underlying transport and
leaves `codex-login` on the temporary direct-`reqwest` allowlist
introduced by #31431.
Auth endpoints also have a stricter logging boundary than ordinary API
requests: custom issuer URLs and response headers may contain
credentials. Moving these requests behind the shared HTTP abstraction
must preserve that boundary while retaining route-aware proxy and
custom-CA behavior.
This is a bounded login migration. The separate Agent Identity and
shared default-client compatibility migrations remain follow-up work.
## What changed
- Add `HttpClientFactory::build_client` to construct the shared
`HttpClient` abstraction for a resolved destination and route class.
- Add a route-aware construction path that suppresses request URL,
response-header, and transport-error diagnostics for sensitive auth
endpoints.
- Route device-code user-code/polling requests, OAuth authorization-code
exchange, and API-key token exchange through `HttpClient`.
- Build the revoke timeout test client through the same factory API.
- Use the transport-neutral `http::StatusCode` in the migrated
device-code flow.
- Add an end-to-end log-capture regression test covering successful
responses and transport failures after `RequestBuilder` transformations.
## Review guidance
The request behavior is intended to be unchanged: each issuer/token
endpoint selects the same auth route, including the existing system/PAC
proxy and custom-CA handling, and raw auth clients still omit Codex
default headers. The intentional logging change is limited to raw auth
requests, whose URL userinfo, query credentials, response headers, and
transport errors must not cross the auth redaction boundary.
This PR deliberately does **not** remove `codex-login` from #31431's
allowlist. The remaining direct `reqwest` surface belongs primarily to:
- Agent Identity APIs that still accept `reqwest::Client`.
- Exported default-client compatibility helpers used by other workspace
crates.
- A small number of tests and concrete error/header types.
## Testing
- `cargo check -p codex-http-client -p codex-login --tests`
- `just test -p codex-http-client` (41 tests)
- `just test -p codex-login` (155 tests)
- `just bazel-lock-check`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/31637).
* #31837
* #31828
* #31825
* #31821
* __->__ #31637
* [codex-apps] Filter optional file fields by tool schema (#31686)
## Summary
Codex Apps file parameters are exposed to the model as local paths,
uploaded at execution time, and rewritten into provided-file payloads
before the MCP tool call.
The rewrite currently includes the documented optional fields
`mime_type` and `file_name` for every file parameter. Apps with strict
schemas can reject those fields when they are not declared.
## Changes
- Derive the supported optional file fields from each
`openai/fileParams` parameter's raw input schema before replacing it
with the model-visible local-path schema.
- Always include `download_url` and `file_id`.
- Include `mime_type` and `file_name` only when that specific file
parameter's schema accepts them, including schemas that allow additional
properties.
- Handle scalar and array file parameters, including items-only arrays,
composed schemas, and local JSON Schema references.
- Preserve the existing restriction that only the host-owned Codex Apps
MCP server can use this upload path.
This supports tools with different file contracts in the same app: one
parameter can accept the optional fields while another remains strict.
## Validation
- `just test -p codex-mcp`
- `just test -p codex-core mcp_openai_file`
- `just test -p codex-core codex_apps_file_params_`
- `just fix -p codex-mcp`
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`
- Manually verified in the Codex Electron app that:
- a strict file schema receives only `download_url` and `file_id`
- a rich file schema also receives `mime_type` and `file_name`
Related: #31330
* code-mode: fall back to using in process v8 if we fail to resolve external process (#31899)
## Why
Not every Codex distribution currently includes the
`codex-code-mode-host` companion binary. Enabling the process-host
feature should not make code mode unavailable on those surfaces while
packaging support is being completed.
## What changed
- Fall back to an in-process code-mode session only when spawning the
companion binary returns `io::ErrorKind::NotFound`.
- Keep permission, handshake, timeout, and other host failures visible
instead of silently falling back.
- Store the provider's owned-process/in-process choice as one
enum-backed state so later sessions reuse the fallback decision.
- Preserve the underlying spawn `io::Error` while retaining the host
path in the displayed error.
- Update provider, `CodeModeService`, and end-to-end coverage to verify
successful fallback execution.
## Test plan
- `just test -p codex-code-mode`
- `just test -p codex-core missing_process_host`
* Bound remote MCP stdio lines (#31805)
## Why
Remote MCP stdio buffers executor-provided stdout until a JSON-RPC line
completes and stderr until a diagnostic line completes. A malicious or
broken executor can keep sending chunks without a newline, making
orchestrator memory grow without bound.
Executor event boundaries are arbitrary, so a valid message before an
oversized line must not be lost merely because both arrived in one
event.
## What changed
- Limit one stdout JSON-RPC line to 8 MiB.
- Limit one stderr diagnostic line to 1 MiB.
- Check each logical line before copying its bytes into the retained
buffer.
- Preserve complete stdout messages that precede an oversized line and
deliver them before closing the stream.
- Close the MCP transport on overflow; existing transport cleanup
terminates the executor-managed process.
- Preserve multiple lines per chunk, CRLF framing, and final
unterminated messages at EOF.
- Box the pending stdio transport so the added framing state does not
inflate every `ClientState` value.
- Add regression coverage for cross-chunk overflow, bounded lines in a
larger chunk, and a valid prefix before an oversized line.
## Scope
This bounds pre-newline buffering for executor-backed MCP stdio only. It
does not change local stdio framing or address memory amplification
while parsing a complete JSON message.
* Resolve and pin MCP OAuth credential stores (#30293)
[Codex Thread
019edd6d-6f14-74e2-853c-345d1803d4a6](https://codex-thread-link.openai.chatgpt-team.site/thread/019edd6d-6f14-74e2-853c-345d1803d4a6)
## Stack
Review and merge in order. Every layer is independently correct and
documents its safe stopping point.
1. [openai/codex#30292](https://github.com/openai/codex/pull/30292) —
aggregate File/Secrets store locking
2. [openai/codex#30293](https://github.com/openai/codex/pull/30293) —
resolve and lifecycle-pin the exact OAuth store
3. [openai/codex#30416](https://github.com/openai/codex/pull/30416) —
serialized authoritative refresh transaction
4. [openai/codex#30294](https://github.com/openai/codex/pull/30294) —
Codex-owned transport refresh and one-shot 401 recovery
5. [openai/codex#30295](https://github.com/openai/codex/pull/30295) —
login/logout transaction serialization
6. [openai/codex#30296](https://github.com/openai/codex/pull/30296) —
diagnostic-only Auto store drift reporting
**This PR is layer 2.**
## Why
`Auto` is keyring-first with a File fallback, but re-evaluating that
policy during transport reconstruction or persistence can make one MCP
client read from one store and later write to another. With rotating
refresh tokens, the second store may contain an older token. This layer
makes the source selected at client startup explicit and keeps that
authority stable for the client lifecycle.
## What this PR does
- Keeps `resolve_oauth_tokens_from_store_policy` as the single
configured-policy entry point and returns both credentials and the
concrete File or Keyring source that supplied them.
- Puts exact `load`, `save`, and `delete` operations on
`ResolvedOAuthCredentialStore`, making “resolve configured policy” and
“use the selected authority” distinct at call sites.
- Pins the first concrete source in `pinned_credential_store` in the
transport recipe, so initialization retries and session reconstruction
cannot re-evaluate `Auto` and adopt another store.
- Gives `OAuthPersistor` the resolved store and keeps subsequent
persistence and removal on that authority.
- Uses a typed keyring-load error to distinguish aggregate-store
coordination failures from ordinary backend failures; a coordination
failure is surfaced instead of triggering File fallback.
- Keeps login-time `Auto` behavior unchanged: prefer Keyring, fall back
to File when unavailable, and clean up legacy File state after a
successful keyring save.
- Adds structured server/backend context when fallback cleanup fails.
## Explicit decisions and non-goals
- The selection is lifecycle-local and in memory. This PR does not add a
durable backend selector, migration, reconciliation registry, or global
source of truth outside `CODEX_HOME`.
- `Auto` may choose File at the start of a later process if keyring
availability changes. Once this client resolves, a selected-store
failure is returned instead of hot-switching.
- Different `CODEX_HOME` instances remain independent even when they can
access the same Direct keyring credential.
- Cross-process refresh serialization is intentionally not part of this
layer.
## Safe stopping point
This PR can merge alone. A single MCP client no longer hot-switches
credential stores across transport rebuilds or persistence. Two
processes can still refresh the same selected credential concurrently
until layer 3.
## Review size
The net layer is 9 files, +668/−144. The production change remains
focused on store resolution and lifecycle pinning; the largest follow-up
is integration coverage that drives real session recovery.
## Validation
- `just test -p codex-rmcp-client` (99 passed; 5 expected skips)
- Real-client 404 recovery coverage with different Keyring and File
tokens; captured bearer headers prove the stale File token is never sent
- Mutation check: removing the lifecycle pin makes that integration
regression fail by observing the stale File token
* exec-server: materialize filesystem workspace roots (#31892)
Filesystem helper requests currently turn symbolic `:workspace_roots`
permissions into a sandbox policy before applying the workspace roots
from the filesystem sandbox context. This can accidentally broaden
filesystem access to the cwd instead of limiting it to the selected
workspace roots.
Materialize project-root permissions using the context workspace roots
before deriving the filesystem sandbox policy. The same converted roots
are then reused when constructing the sandbox command, keeping policy
enforcement and process setup aligned.
Adds a remote filesystem integration test that verifies a file inside
the selected workspace root is readable while a sibling under the cwd is
denied.
## Validation
- `just test -p codex-exec-server
remote_read_file_materializes_environment_workspace_roots` (macOS,
outside the outer Seatbelt sandbox)
* feat: add managed Bedrock login API (#31327)
## Why
App-server clients can report whether Amazon Bedrock is using
AWS-managed credentials or a Codex-managed API key, but they do not have
a matching API for creating the managed login. This PR defines that
experimental wire contract independently from its implementation.
Managed Bedrock API keys are already a primary `CodexAuth` mode. The API
therefore describes a normal Codex login that replaces the current
stored auth rather than introducing provider-scoped credential storage.
## What changed
- Add the experimental `amazonBedrock` variant to `account/login/start`.
- Accept an API key and AWS region and return a matching discriminated
response.
- Gate the request behind the app-server `experimentalApi` capability.
- Regenerate the JSON and TypeScript protocol schemas.
- Document the login contract, notifications, primary-auth replacement
semantics, restart boundary, and non-transactional durable writes.
## Impact
This PR defines the API shape but does not implement login behavior. The
next PR adds validation, persistence through the existing Codex auth
lifecycle, provider selection, and notifications.
## Validation
- `just test -p codex-app-server-protocol`
## Stack
1. **#31327 Managed Bedrock experimental API** — base: `main`
2. #31326 Managed Bedrock login — base: `codex/managed-bedrock-api`
3. #31325 Managed Bedrock logout — base:
`codex/managed-bedrock-login-v2`
* bench: add codex help e2e macrobenchmark (#31295)
## Why
Bazel-backed end-to-end macrobenchmark plumbing needs a small,
deterministic first consumer that does not couple the shared
infrastructure to the remote-skill scenario.
## What
- add `codex_e2e_benchmark`, a small macro for Bazel-only Divan
benchmarks and runtime binary runfiles
- keep benchmark sources under `e2e_benches/` so Cargo does not
auto-discover them
- add a CLI example that resolves the real `codex` binary and measures
`codex --help`
- assert the spawned command succeeds
## Validation
- `bazel test --compilation_mode=fastbuild
--@rules_rust//rust/settings:extra_rustc_flag=-Cdebug-assertions=no
--@rules_rust//rust/settings:extra_exec_rustc_flag=-Cdebug-assertions=no
--cache_test_results=no --test_output=errors --test_arg=--test
//codex-rs/cli:codex-help-bench`
## Stack
1. [#31295 bench: add codex help e2e
macrobenchmark](https://github.com/openai/codex/pull/31295)
2. [#31428 bench: add e2e benchmark
entrypoints](https://github.com/openai/codex/pull/31428)
3. [#31429 ci: smoke Bazel e2e
benchmarks](https://github.com/openai/codex/pull/31429)
* bench: add e2e benchmark entrypoints (#31428)
## Why
Bazel-backed macrobenchmarks need stable local entrypoints that can grow
without changing every caller.
## What
- add a top-level `e2e-benchmarks` Bazel suite, initially containing the
`codex --help` benchmark
- add cross-platform `just bench-e2e` for direct optimized Bazel
measurements
- add cross-platform `just bench-e2e-smoke` for one direct fastbuild
iteration through release-only Rust cfg paths
## Validation
- `just --dry-run bench-e2e`
- `just --dry-run bench-e2e-smoke`
- `just bench-e2e-smoke`
## Stack
1. [#31295 bench: add codex help e2e
macrobenchmark](https://github.com/openai/codex/pull/31295)
2. [#31428 bench: add e2e benchmark
entrypoints](https://github.com/openai/codex/pull/31428)
3. [#31429 ci: smoke Bazel e2e
benchmarks](https://github.com/openai/codex/pull/31429)
* exec-server: expose process helper to outer sandbox (#31937)
## Why
Sandboxed exec-server process requests can use a restricted filesystem
profile that does not expose the exec-server binary. On Linux, the outer
bubblewrap stage re-enters that binary with the `codex-linux-sandbox`
argv0 to install seccomp, so hiding the binary prevents the requested
process from starting.
## What changed
- add the configured `codex_self_exe` to the process permission profile
before constructing the outer platform sandbox
- add a Linux exec-server integration test that starts a real remote
process with restricted reads and verifies it can read an allowed
workspace file
## Test plan
- `just test -p codex-exec-server process_sandbox`
- `just test -p codex-exec-server --test exec_process
remote_process_keeps_sandbox_helper_visible_with_restricted_reads`
* Initialize Copyberry export baseline
GitOrigin-RevId: 313afeef691aff7819ccd4bdb1e351438c8b40e4
* Remove the legacy exec policy engine (#32093)
## What changed
- Delete the `codex-execpolicy-legacy` crate, its default policy, and its tests.
- Remove the crate from the Cargo and Bazel workspaces and clean up its unused dependencies.
- Remove the legacy matcher reference from the current exec policy documentation.
GitOrigin-RevId: ee756958b46bf759ad43dec8aa1379c1cd4d8b0d
* Reduce startup latency for ancestor discovery (#32106)
## Why
Serial metadata probes add remote round trips while Codex discovers project
instructions, repository skills, and project-root markers during startup.
## What changed
- Probe ancestor directories concurrently while preserving discovery order.
- Raise the shared find-up probe window to cover typical project hierarchies.
- Refresh project instructions in parallel with plugin and skill warmup.
## Testing
Add coverage that blocks one metadata request and verifies all expected project-root
and `AGENTS.md` ancestor probes have started before it is released.
GitOrigin-RevId: 11449837c7a212ec8ed385e7290476e926d93531
* Bound streamed exec-server HTTP response bodies (#32112)
## Why
Frame-count backpressure does not bound the amount of executor-controlled data
retained in streamed HTTP response queues, and a single body delta could exceed
the intended wire size.
## What changed
- Limit each decoded `http/request/bodyDelta` payload to 1 MiB, split locally
produced response chunks at that boundary, and reject oversized incoming
deltas.
- Apply a shared 16 MiB byte budget across queued HTTP response streams. Release
capacity as deltas are consumed and fail a stream when the budget is
exhausted.
## Testing
Added coverage for rejecting an oversized delta and for failing a stream after
its queued deltas exhaust the shared byte budget.
GitOrigin-RevId: be9205fef44b0ec96b84b31a8e059b1cb9cd3f3b
* Test the shared exec-server HTTP response byte budget (#32122)
## Testing
Add a concurrent-stream regression test that fills the connection-wide queued
body budget across two HTTP responses. Verify that the overflowing stream
reports the byte-budget error while the other stream still drains successfully.
GitOrigin-RevId: 65efacb15f1368b1de3b98e7ef65ebca66a3840b
* Bound exec-server stdio JSON-RPC messages (#32123)
## Why
Newline-delimited stdio input could buffer an unterminated JSON-RPC message without a per-message limit. Apply the same 64 MiB ceiling used by the other exec-server transports.
## What changed
- Read stdio messages with bounded lookahead and disconnect when a message exceeds the limit.
- Preserve LF and CRLF framing, including messages whose payload is exactly at the limit.
## Testing
Added tests for accepting a limit-sized CRLF message and rejecting an unterminated overlong message.
GitOrigin-RevId: 73eaf883324a7777960725037cc5b9c34720084f
* Test bounded concurrency in ancestor discovery (#32126)
## What changed
Add regression coverage that verifies ancestor metadata searches:
- keep at most 256 project-root marker or repository skill-root probes in flight;
- start the next probe as capacity becomes available; and
- preserve `AGENTS.md` and repository skill-root discovery order.
GitOrigin-RevId: e7ac90bbeb94573d01e67705001af85ce59165b6
* Test stdio JSON-RPC size limits with LF and CRLF (#32134)
## What changed
Update the exact-size-limit stdio connection test to exercise both `\n` and
`\r\n` line endings, sizing the duplex buffer for each delimiter.
GitOrigin-RevId: 949e28bef3f1a34d379f3623f6aa7d99c0ecea80
* Propagate tracing subscribers to exec start tasks (#32135)
## Why
The process-start background task can move to a Tokio worker thread, where the
caller's thread-local tracing subscriber is otherwise unavailable. This can
break propagation of the caller's trace context to the exec-server request.
## What changed
- Attach both the current span and current tracing subscriber to spawned
process-start tasks.
- Run exec-server Bazel unit tests serially because their tracing setup uses
process-global state.
## Testing
Run the trace-context regression test on a multi-thread Tokio runtime so it
exercises propagation across the background task.
GitOrigin-RevId: a586eefd6983916d670fc5a90f0decd0468d273b
* Keep unified exec output collection bounded (#32150)
## Why
Unified exec can drain process output multiple times while waiting for a command. Collecting those drains into an uncapped buffer allows large commands to exceed the output collection limit.
## What changed
- Accumulate drained output through the capped head/tail buffer so collection remains bounded while preserving the beginning and end of the stream.
- Include a `... N bytes omitted ...` marker when the collection cap drops output.
- Calculate the original token estimate from all observed bytes and preserve omission metadata through truncation and sandbox-denial responses.
## Testing
- Cover repeated drains, previously omitted output, omission markers, and large-output summaries.
GitOrigin-RevId: c6e2efeb875ed8f35914365eab7431f25fd3484c
* Validate memory consolidation artifacts before succeeding (#32193)
## Why
A completed Phase 2 agent run does not guarantee that its required outputs were
created correctly. Treating a clean workspace as success can also preserve a
state where those outputs are missing.
## What changed
- Require `MEMORY.md` to be a file and `memory_summary.md` to start with `v1`
before marking consolidation successful.
- Fail completed runs with invalid artifacts without resetting the workspace
baseline, allowing the job to be retried.
- Run consolidation for a clean workspace when its required artifacts are
invalid instead of taking the no-change success path.
## Testing
Added coverage for rejecting an invalid summary and retrying a clean workspace
whose consolidation artifacts are missing.
GitOrigin-RevId: ac57b2ba9d062c5203ca51afd716795a91814bb3
* Rebind memory consolidation workspace roots (#32197)
## Why
The memory consolidation agent changes its working directory to the memory
root, but applying its sandbox policy directly to the permissions object can
leave workspace roots inherited from the parent configuration.
## What changed
Apply the consolidation sandbox policy through `Config` so its workspace roots
are synchronized with the memory root. Add a test covering the working
directory, workspace roots, and effective legacy sandbox policy.
GitOrigin-RevId: dc6e75aef1ffa84a2f66fec6ed3a6fedcf9aa672
* Add a skill invocation extension contributor (#32200)
## What changed
- Add `SkillInvocationContributor` and registration support to the extension API.
- Provide invocation callbacks with session-, thread-, and turn-scoped extension data, the turn ID, skill resource, and explicit or implicit invocation kind.
- Notify registered contributors when Codex observes a deduplicated implicit skill invocation.
## Testing
- Extend the extension registry round-trip test to cover skill invocation contributors.
GitOrigin-RevId: f428336728889d9bf4fba41a7dd8d3a6f9b9728c
* Always send reasoning parameters in Responses requests (#32206)
## What changed
- Build a reasoning payload for every Responses request and always include `reasoning.encrypted_content`.
- Remove `supports_reasoning_summaries` from model metadata and retire the `model_supports_reasoning_summaries` configuration override.
- Use configured or model-default reasoning effort without a capability gate, including for guardian reviews and tracing.
GitOrigin-RevId: 2c9f194a5d2d4d688a2235299e6358f82ab8e1ea
* Generate unique IDs for review rollout messages (#32213)
## What changed
Assign a fresh UUIDv7 to each synthesized user and assistant message recorded
when exiting review mode, instead of reusing fixed IDs across review runs.
GitOrigin-RevId: 8524176895e2d8c750760554f515f397728a8554
* Propagate workspace roots to exec-server sandboxes (#32214)
## What changed
- Pass configured workspace roots from core to the exec server so filesystem and process sandbox permissions are materialized against the intended roots.
- Preserve an explicitly empty workspace-root list instead of treating the sandbox working directory as an implicit root.
- Initialize filesystem sandbox contexts with their working directory as the default workspace root.
## Testing
- Add end-to-end coverage for patch and command writes inside and outside workspace roots.
- Verify remote filesystem and process sandboxes do not grant …
Codex Thread 019edd6d-6f14-74e2-853c-345d1803d4a6
Stack
Review and merge in order. Every layer is independently correct and documents its safe stopping point.
This PR is layer 6.
Why
Auto is intentionally resolved once and pinned only for a client lifecycle in this stack. If keyring availability differs in a later process, Auto can resolve to a different store. We do not want to add a durable credential-authority selector or migration policy in this stack, but we do want evidence if this rare assumption fails in practice.
What this PR does
$CODEX_HOME/.mcp-oauth-store-resolutions.json.compute_store_key(server_name, url)identity used by credential locking.codex.mcp.oauth.store_resolution_changedcounter when repeated Auto resolution under the same backend changes stores.Explicit decisions
CODEX_HOME; different homes are intentionally independent.Safe stopping point
This PR changes observability only. The five correctness layers below it are independently complete without this diagnostic.
Validation
just test -p codex-rmcp-client(116 passed; expected environment skips)