## Why
Multi-environment turns can run against different filesystems, so a single
thread-wide workspace-root list cannot accurately describe every selected
environment.
## What changed
- Add optional `runtimeWorkspaceRoots` to each app-server environment selection.
Omitted roots default to that environment's `cwd`, while an empty list selects
no roots.
- Carry environment-native roots through turn selection, model-visible context,
permission materialization, and tool sandboxing.
- Keep the top-level `runtimeWorkspaceRoots` field as a compatibility input when
constructing default environment selections; explicit selections use their
own roots.
## Testing
- Cover thread and turn API defaults, overrides, empty roots, and schema
round-tripping.
- Verify foreign roots remain model-visible and remote execution applies the
target environment's roots before sandbox selection.
GitOrigin-RevId: c6e7a62883c45c676c153055f46a55ee15a323a4
## Why
Embedding callers need to control whether model catalogs are persisted to disk.
## What changed
- Accept a shared models manager when constructing `ThreadManager` instead of always creating a cached manager internally.
- Add provider APIs for creating uncached model managers. OpenAI-compatible providers fetch on each `OnlineIfUncached` refresh without reading or writing `models_cache.json`.
- Keep existing app server, MCP server, sample, and test callers on the standard cached manager.
## Testing
- Verify an uncached manager fetches on every refresh.
- Verify an injected uncached manager controls thread refresh behavior and does not create `models_cache.json`.
GitOrigin-RevId: 8bb646054ecec4ccb865b2bf56249384916ea9db
## Why
Policy-blocked proxy requests must terminate the corresponding active tool call and preserve the correct approval result, including when multiple calls are running concurrently.
## What changed
- Resolve a blocked request's execution ID to its registered active network call before recording the policy denial.
- Preserve an outcome already recorded for that call, and cancel the owning call after storing a new denial.
## Testing
Expand network approval integration coverage for Guardian and user decisions, permission hooks, persisted policy amendments, concurrent calls, timeouts, cancellations, and unattributed proxy requests.
GitOrigin-RevId: 961901bbc07cc498309453ec78cda1a56ab00a4b
## Why
Compacting a resumed conversation can fail when its previous model is no longer
available. Some model-specific failures, such as a model-not-found response, are
not represented as invalid requests and therefore did not trigger the existing
fallback to the currently selected model.
## What changed
- Share a retry predicate between both remote compaction implementations.
- Fall back to the current model for invalid requests, unexpected statuses,
context-window and usage-limit errors, server failures, and exhausted retries.
## Testing
Add an integration test that resumes a conversation whose previous model returns
model-not-found and verifies that compaction and the next turn use the renamed
model.
GitOrigin-RevId: c3857beae2ce79b50589deb13c2e59710ce14725
## What changed
- Add an `auto_review.policy` field to model catalog messages.
- Use the selected Guardian model's catalog policy for review-session instructions, while preserving the precedence of `guardian_policy_config` and falling back to the built-in policy when neither is present.
- Preserve auto-review messages when model instruction overrides remove catalog instruction templates.
## Testing
- Cover configured-policy precedence, explicit empty catalog policies, catalog-message preservation, and propagation of the catalog policy into a prewarmed Guardian session.
GitOrigin-RevId: 26b61ae2958ea8325a64834dcf91f47e140d74b3
## Why
Network proxy enforcement on Windows requires the elevated sandbox backend. Silently selecting that backend for an unelevated configuration makes the effective sandbox differ from the configured mode.
## What changed
- Reject enabled network proxy configurations on Windows unless managed requirements allow only the elevated sandbox and `windows.sandbox` resolves to `"elevated"`.
- Preserve the configured sandbox backend during execution and validate proxy compatibility at config loading, sandbox setup, and process launch boundaries.
- Defer this validation during cloud-config bootstrap so authoritative managed requirements can be loaded first.
- Do not start or expose disabled Windows proxies, and render transitions to disabled networking as `<network enabled="false"></network>`.
## Testing
- Cover Windows requirement and sandbox compatibility matrices, bootstrap behavior, backend selection, disabled proxy handling, and environment-context updates.
GitOrigin-RevId: bfa1dda98594e0db61883f8e7f65bd560e3453e6
## Why
After a cold resume, a Multi-Agent V2 root thread must still be able to
address its persisted descendants even though their runtimes are not reopened
eagerly.
## What changed
- Restore metadata for open descendant agents when resuming a V2 root thread.
- Preserve each descendant's path, nickname, and role while leaving its runtime
unloaded until a message targets it.
- Reuse the spawn metadata preparation path so restored identities retain the
same reservation and naming checks as newly spawned agents.
## Testing
Added an end-to-end cold-resume test that follows up with a persisted agent by
name and verifies that the original agent thread is loaded on demand.
GitOrigin-RevId: a1663c1ae60eefadb948cd39f933cd3bbb49a492
## Why
OAuth metadata discovery for an optional HTTP MCP server can stall, delaying
`thread/start` even though that server is not required.
## What changed
- Remove eager MCP authentication-status discovery from session and connection
manager startup.
- When an MCP connection reports an authentication error, inspect configured and
stored credentials locally to preserve login and reauthentication failure
reasons without making another discovery request.
- Continue using server configuration directly for startup error messages and
timeout hints.
## Testing
Add an app-server regression test with an optional HTTP MCP endpoint that accepts
a connection but never responds, and verify that `thread/start` completes without
waiting for its configured startup timeout.
GitOrigin-RevId: 56fe0188cc1ceaf746b0b65e8b7dd1fd2e13420d
## Why
After switching from proactive delegation to explicit-request-only mode, an
earlier proactive instruction can remain in the conversation context. The new
mode must clearly supersede that instruction.
## What changed
Update the explicit-request-only developer message to cancel earlier proactive
delegation instructions before requiring delegation to be explicitly requested
by the user, `AGENTS.md`, or a skill.
## Testing
Update the multi-agent mode integration test expectation for the explicit-mode
instruction emitted after a cold resume.
GitOrigin-RevId: 0a6d18f38ef82d29cf4f3bfcfd4ac84738a39598
## Why
Paginated thread history needs durable ordering so consumers can process a rollout suffix without rebuilding all earlier history.
## What changed
- Add optional, zero-based ordinals to `RolloutLine` records in paginated rollouts while leaving legacy rollout serialization unchanged.
- Continue ordinals from the last valid record when appending or resuming, including after gaps or an incomplete tail, and reject overflow without appending.
- Add a stateless `project_rollout_line` helper that maps canonical turn lifecycle and completed-item records into thread-history change sets.
## Testing
- Cover ordinal assignment, legacy compatibility, resume and tail recovery, overflow handling, and thread-history projection for completed, failed, and interrupted turns.
GitOrigin-RevId: 3a9bb6cd2a1f674a9f154342e96e9aa3d8330781
## What changed
- Add a `ResponseItemId` type that generates item-specific prefixes with UUIDv7 suffixes and use it across response items.
- Keep deserialization permissive for legacy histories, but omit empty or unprefixed item IDs from HTTP and WebSocket requests.
- Export the new type in the generated TypeScript protocol schema.
## Testing
- Cover prefixed ID generation, legacy deserialization, prefix recognition, and outbound request filtering.
GitOrigin-RevId: 0209fe430c826d0ae88bc4648652c4ebf390c4a4
## What changed
- Add `supports_reasoning_summary_parameter` to model metadata, defaulting to
`true` for backward compatibility.
- Omit `reasoning.summary` and its summary-delivery stream option when the
selected model does not support the parameter.
- Apply the capability of the final selected model when a spawned agent uses a
different model.
## Testing
- Cover unsupported models in regular requests and spawned-agent model
overrides.
GitOrigin-RevId: 72b783799fc0685cef1501ef2dbf62d1308ceead
## What changed
- Add an optional `error` payload to `TurnCompleteEvent` and omit it when a turn completes without an error.
- Preserve the full terminal `ErrorEvent` through the turn lifecycle so the completion event includes the same error details emitted separately by `EventMsg::Error`.
## Testing
- Extend the stream-error integration test to verify that `TurnCompleteEvent` contains the emitted error and that the next turn can still proceed.
GitOrigin-RevId: 8d32942a44132763076130102d8b89ab356d59d0
## Why
Model catalog instructions can include a baked-in `# Personality` section. An
explicit `none` setting should omit that section instead of sending it as part
of the model's base instructions.
## What changed
- Pass the configured personality into the models manager.
- When personality support is enabled and the setting is explicitly `none`,
remove the `# Personality` section through the next level-one heading from
catalog base instructions and instruction templates.
- Preserve explicit `base_instructions` overrides and avoid warning when no
personality was requested.
## Testing
Added unit and integration coverage for section removal, heading boundaries,
CRLF input, preserved configurations, and explicit base instructions.
GitOrigin-RevId: 452c88d3ac6001c2ac7d4fef269cd75dc239fa61
## What changed
- Stop the TUI and app server from inspecting existing sessions at startup to
set `personality = "pragmatic"` automatically.
- Remove the migration marker, helper APIs, and associated tests.
GitOrigin-RevId: 9e963bf113aff21651696bbc463f2749ac0f510a
## What changed
- Add an optional `started_at` Unix timestamp to `TurnCompleteEvent` and
`TurnAbortedEvent`.
- Populate the timestamp from turn timing state when a turn completes or is
aborted.
- Preserve the timestamp when synthesizing interrupted fork history and when
importing external sessions.
## Testing
- Verify that interrupting a turn emits a `TurnAbortedEvent` with a start
timestamp.
GitOrigin-RevId: 75b3911b95c060e5fc1843a5e293d8fe47377186
## What changed
- Emit `responsesapi.websocket_timing` payloads as opt-in trace events with request context, while excluding them from diagnostic uploads and persisted logs by default.
- Preserve fractional-millisecond TBT values in telemetry histograms and runtime summaries, rounding only when formatting the TUI label.
## Testing
- Cover timing-log filtering, fractional TBT collection and display, and existing whole-millisecond duration behavior.
GitOrigin-RevId: 5f0a1c60c237abed17cc8b2d569ca77fbbc6ec41
## What changed
- Run permission-request hooks before routing approvals to the automated reviewer or user, including when `strict_auto_review` is enabled.
- Centralize approval resolution so hook, automated-reviewer, and user decisions share rejection handling and report the correct telemetry source.
## Testing
- Add an integration test showing that an allow hook can approve a shell command during strict auto-review without invoking the automated reviewer.
GitOrigin-RevId: 18e9d76baee6cbae9d35ae517250f2f82270a9d2
## Why
Remote environment provisioning can finish after a thread starts, before an
exec-server WebSocket URL is available.
## What changed
- Add `EnvironmentManager::register_pending_environment` and a one-shot
`PendingEnvironmentRegistration` handle that resolves to either a validated
WebSocket URL or a terminal provisioning error.
- Let lazy remote exec-server clients wait for that result, while preserving
reconnection behavior after a successful registration.
- Keep replacement registrations isolated so completing an older handle does
not resolve the current environment with the same ID.
## Testing
Add coverage for successful connection and reconnection, provisioning and
dropped-registration failures, invalid URLs, replacement isolation, and the
deferred-executor startup flow.
GitOrigin-RevId: 5c05be2b72291b77a1f71176d7075b1ad63332a5
## 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 access through an empty workspace-root list.
GitOrigin-RevId: 6684c8f7de50970b45a29e2a6323df954c96f9f6
## 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
## 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
## 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`
## 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
## 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`
## 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`
## 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.
## 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.
[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.
## 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
## 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)
## 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`
### 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.
## Why
Responses WebSockets are the normal lower-latency transport for
WebSocket-capable providers. They must not bypass an OS-selected proxy
when `features.respect_system_proxy` is enabled, but disabling
WebSockets whenever the feature is enabled would impose a substantial
performance penalty.
Merged PR #31622 introduced the reusable proxy-aware WebSocket
transport. This PR makes the Responses API its first consumer so the
existing fast path uses the same effective proxy and trust policy as
HTTP.
## What changed
- Register `codex-websocket-client` as a workspace dependency and use it
from `codex-api`.
- Feed the shared crate’s route-independent `WebSocketConnection` into
the existing Responses message pump.
- Require a configured `HttpClientFactory` for normal Responses
WebSocket connections and the CLI doctor probe, so neither path can open
a connection without consulting the effective proxy policy.
- Pass the session factory from `core` and the effective configuration
factory from `doctor`.
- Add an end-to-end Responses test that enables `RespectSystemProxy`,
asserts the resolved policy, completes a turn over WebSocket, and
verifies the connection and request counts.
- Keep the existing Responses protocol handling, ping/pong pump, and
session-scoped HTTP fallback unchanged.
The DNS, proxy, TLS, custom-CA, and Happy Eyeballs implementation and
its transport tests live in merged PR #31622. This PR deliberately
contains only the Responses integration and does not duplicate that
transport code.
## Review guide
1. `codex-rs/codex-api/src/endpoint/responses_websocket.rs` constructs
the shared connector and adapts its uniform stream to the existing pump.
2. `codex-rs/core/src/client.rs` supplies the session-scoped factory for
production Responses connections.
3. `codex-rs/cli/src/doctor.rs` supplies the effective configuration
factory to the handshake probe.
4. `codex-rs/core/tests/suite/client_websockets.rs` covers the
enabled-feature path end to end.
## Test plan
- `cargo check --tests -p codex-api -p codex-core -p codex-cli`
- `just test -p codex-api`
- `just test -p codex-core
responses_websocket_streams_with_system_proxy_feature`
- `cargo shear`
- `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/31441).
* #31637
* #31431
* #31363
* #31362
* #31361
* __->__ #31441
## Summary
- Promote code_mode_host to stable and enable it by default.
- Preserve features.code_mode_host = false as an opt-out to the
in-process runtime.
- Run core code-mode tests through the standalone host.
- Keep explicit coverage for missing-host failures.
## Description
This PR moves review-mode markers onto canonical `TurnItem` lifecycle:
- `TurnItem::EnteredReviewMode`
- `TurnItem::ExitedReviewMode`
Core now emits `ItemStarted` / `ItemCompleted` for both. The completed
items map back into the existing `EnteredReviewMode` /
`ExitedReviewMode` events, so raw core event consumers and legacy
rollout persistence keep seeing the old events.
This is the compatibility layer needed before paginated rollouts persist
review markers as `ItemCompleted(TurnItem)`.
## Why
Review markers were one of the remaining app-server thread items created
directly from legacy events. Giving them canonical items lets paginated
history persist stable turn/item IDs without changing legacy rollouts.
## What changed
- Added canonical review-mode `TurnItem`s and switched review flow to
emit their lifecycle.
- Added completed-item → legacy review event mappings with stable
turn/item IDs.
- Switched app-server live notifications to the generic canonical item
path and kept legacy replay compatible with old payloads.
- Updated `ThreadHistoryBuilder` to replay canonical review items even
though review turns still do not emit `TurnStarted`.
## Why
`subagent_usage_draws_from_the_shared_budget` intermittently fails even
when the shared-budget behavior is correct. `ResponseMock` records a
request before the custom Wiremock predicate is checked, so the
follow-up mock can also contain unrelated requests. In a [recent Windows
ARM64 run](https://github.com/openai/codex/actions/runs/28916285431),
`single_request()` saw all seven requests from the scenario.
## What changed
Select the request containing the follow-up user prompt before making
assertions. The test still requires exactly one matching follow-up
request and still checks that the root sees 50 tokens remaining after
the child uses its share.
This is test-only. Shared-budget behavior and the common response-mock
helper are unchanged.
## Why
The remote compaction parity test compares legacy and v2 sessions
created with separate temporary homes. Those sessions can discover
different model-visible skill catalogs, so the request comparison can
fail even when the compaction and service-tier behavior matches.
This is the most frequent retry-saved full-CI failure in the recent
JUnit history.
## What changed
Normalize only the contents of `<skills_instructions>` before comparing
the captured requests. The opening and closing tags remain in the
comparison, so the test still catches a missing or misplaced skills
block.
The service-tier, compacted input, follow-up request, and
replacement-history assertions are unchanged. A focused normalizer test
covers the new behavior.
## Scope
This is test-only. It does not change runtime compaction or skill
behavior. Exact skill-catalog rendering remains covered by the dedicated
skills tests.
## Why
The encrypted MAv2 spawn test often reads the parent follow-up request
before the child has sent its first request. The response mock records
candidate requests before applying its specific matcher, so the test can
see an empty `agent_message` list even though delivery happens a moment
later.
## What changed
Wait for the recorded child request that contains `agent_message`, using
the same short bounded polling pattern already used in this test module.
The exact encrypted payload and communication-log assertions stay
unchanged.
This is test-only: it does not add product delays, loosen the assertion,
or change multi-agent behavior. No follow-up is expected.
## Summary
- Rename the hosted web-search wire field to the canonical
`indexed_web_access` spelling.
- Preserve existing indexed-search behavior.
## Rollout
Merge and release only after server support for `indexed_web_access` is
fully deployed.
## Testing
- `just fmt`
- `just test -p codex-tools
web_search_tool_spec_serializes_expected_wire_shape`
- Blocking CI passed, including indexed web-search integration coverage.
## Why
Pre-sampling compaction intentionally uses the previous turn's model
when the compaction compatibility hash changes or when switching to a
model with a smaller context window. This keeps compaction aligned with
the settings that produced the history, but it can block the next turn
when a resumed ChatGPT thread still references a model slug that has
since been retired. The Codex backend rejects that compaction request
before the user's currently selected model gets a chance to sample.
This PR lets those threads recover without changing previous-model
compaction behavior for API-key authentication or custom providers. It
is stacked on #31316, which is a behavior-preserving extraction of the
individual remote compaction attempts; this PR contains the fallback
behavior.
## What changed
- For automatic previous-model compaction, capture the selected model's
request context when using ChatGPT authentication with the OpenAI
provider and the selected model differs from the previous model.
- If the previous-model attempt returns an `InvalidRequest`, retry
compaction once with the selected model for both `/responses/compact`
and Responses Compaction V2.
- Complete history processing, lifecycle events, and token accounting
with the context of the model that successfully compacted the thread.
- If the fallback also fails, return the original previous-model error
so the retry does not change the user-visible failure.
- Record fallback attempts with reason, implementation, and outcome
telemetry.
- Leave API-key authentication, custom providers, same-model turns, and
non-`InvalidRequest` failures on their existing paths.
## Testing
- `just test -p codex-core -E 'test(pre_sampling_compact) |
test(model_unavailable_error)'` (10 tests)
- Added integration coverage for a resumed thread whose model was
renamed, a model downshift using Responses Compaction V2, and API-key
authentication with a custom provider.
## Summary
- enable `auth_elicitation` by default
- promote the feature to `Stable`, as default-enabled features must be
stable
- update the feature regression test to cover the new lifecycle state
and default
## Impact
Auth elicitation is now available without requiring users or clients to
opt in through configuration.
## Testing
- `just test -p codex-features` (52 passed)
## Why
Approval guidance is currently assembled entirely by the client. Model
Messages V2 needs model catalogs to provide model-specific `on_request`
guidance for both user-reviewed and auto-reviewed approval flows while
retaining the existing generated prompt as a compatibility fallback.
## What changed
- add nullable `on_request` and `on_request_auto_review` catalog
messages
- select the message matching the active approvals reviewer for
`on_request` policies
- replace the complete legacy approval section when the selected catalog
value exists, including support for an empty string that suppresses the
section
- retain legacy rendering when the object or selected key is absent, and
for non-`on_request` policies
- preserve approval messages when base-instruction or personality
overrides clear instruction templates
- refresh permissions instructions when the active model changes
- pass catalog messages through initial and incremental permissions
construction
## Relationship to reviewer persistence
PR #31309 independently persists the approvals reviewer in turn context
and refreshes permissions when that reviewer changes. This PR is based
directly on `main` and does not duplicate that rollout migration; once
both land, reviewer switches will also select and append the new catalog
variant.
## Testing
- `just test -p codex-protocol`
- `just test -p codex-prompts`
- `just test -p codex-models-manager`
- `just test -p codex-core permissions_messages`
## Summary
- Adds `writes` to `AppToolApproval` and exposes it through config and
app-server schemas, including
`[apps._default].default_tools_approval_mode`.
- In `writes`, tools with `readOnlyHint = true` skip approval; all other
tools prompt, including non-destructive writes and tools without
annotations.
- Prevents session or persistent approval choices in this mode so later
writes still prompt.
## Why
`auto` only prompts for risk-hinted actions, while `prompt` also
interrupts reads. Apps need a middle mode that gates writes without
prompting for declared read-only actions.
## Validation
- `just write-config-schema`
- `just write-app-server-schema`
- `just fmt`
- `just test -p codex-core mcp_turn_metadata` (4 passed)
- `just test -p codex-core writes_mode` (2 passed)
- `just test -p codex-app-server config_read_includes_apps` (1 passed)
- `just test -p codex-app-server-protocol` (251 passed)
- `just test -p codex-config` (200 passed)
- `just test -p codex-cli` (300 passed)
- `just fix -p codex-core -p codex-config -p codex-app-server-protocol
-p codex-app-server -p codex-cli`