## What changed
- Record executor handshake rejections and pending-validation resets in promptly completed diagnostic spans, with classified reasons and registry HTTP status when available. Normalize stream IDs as UUIDs and cap diagnostics at 100 per minute per relay connection, followed by a suppression marker.
- Add elapsed time and connection identifiers to harness handshake diagnostics, including initialization timeouts, and classify WebSocket read failures from typed errors.
- Forward valid stream UUIDs to harness key validation requests through the `x-codex-noise-stream-id` header.
## Testing
Extend tests to cover diagnostic rate limits, timeout correlation, prompt span export, typed WebSocket error classification, and optional stream ID headers. Verify that diagnostic output excludes authorization secrets, registry response text, reset reasons, and invalid stream IDs.
GitOrigin-RevId: 2c224e2d2828b300164868b10806add410e148b2
## Why
The handshake readiness channel only signals success, so a dropped sender does not explain why the handshake failed.
## What changed
Emit structured `codex.exec_server.noise_handshake_failed` warnings before harness handshake failures drop the readiness sender, including a failure reason and environment, executor registration, and stream identifiers. Emit the same event on handshake timeout with `timeout_ms`.
Preserve the current tracing span when the `noise_relay.stream` debug span is disabled so the relay task retains its trace context.
GitOrigin-RevId: 0b1314d5237b7c77a854c2c0a966391bf856e75d
## Why
Noise handshake failures before connection readiness returned a generic error even when the transport had reported a specific disconnect reason. No `RpcClient` was reading the connection event channel at that point.
## What changed
Read the pending disconnect event when a Noise rendezvous handshake fails and append its reason to the handshake error when available.
## Testing
Add a regression test covering unexpected text frames and relay stream resets, checking that each produces the expected transport reason in the handshake error.
GitOrigin-RevId: 9b8ef9c8dcba75b46d51dc67784af46d4ca74b85
## What changed
Expose `Environment::observe_connection_attempts` and `ConnectionAttemptOutcome` to report elapsed time and success, failure, or cancellation for initial connections. Measure from the end of provisioning through client installation, including connection retries.
The first registered observer wins. Past attempts and reconnections are omitted.
## Testing
Extend tests to assert failure followed by success when startup is retried, and a single cancellation notification when an environment is replaced during startup.
GitOrigin-RevId: 88300f38dcf9428b824c5ff36aa1b40d7d466b6f
## Why
Background helpers launched from a detached Windows process can allocate console windows. Job Object launches and containment fallbacks need to preserve console suppression as well.
## What changed
- Add `codex_utils_process::background_command` to set `CREATE_NO_WINDOW` on Windows, and use it across background subprocess launches, including Git, plugins, authentication, diagnostics, and sandbox helpers.
- Centralize console suppression in Job Object launches. Keep `CREATE_NO_WINDOW` when retrying without containment, and clear `CREATE_SUSPENDED` so the fallback child can run.
- Preserve console inheritance for interactive shell launches and interactive AWS authentication refreshes.
## Testing
Add Windows regression tests covering console-free launches from detached parents through standard and Tokio commands, stdout/stderr capture, successful containment, Job Object creation and assignment failures, and sandbox setup refresh helpers.
GitOrigin-RevId: b2c238fa7d4f9fbd868e5adf7d4be9958ff01e6c
## Why
A provisioned executor can still be resuming after readiness is reported. Initial connection attempts can exhaust the ordinary registry retry limits before the executor comes online.
## What changed
Retry `environment_offline` registry responses during initial Noise rendezvous connections for provisioned environments until a fixed five-minute deadline, starting after provisioning succeeds. Keep the existing retry limits for other registry errors and stop on permanent failures.
## Testing
Add tests covering the five-minute deadline, ordinary limits for other errors and stalled requests, and termination on a later permanent error. Verify that readiness and info requests remain pending through an extended offline period and succeed using the same environment handle once the executor comes online.
GitOrigin-RevId: e22211499d9ec2f3590e75224eb39e2e4bf3538d
## What changed
- Enforce an 8 KiB encoded-message limit for requests, unrecognized notifications, and malformed messages received by exec-server clients over stdio, WebSocket, relay, and Noise relay transports.
- Allow process output, exit, close, and HTTP request body notifications up to 2 MiB. Keep responses and errors subject to existing transport limits, and preserve executor-side request limits.
- Read stdio stderr in chunks of at most 8 KiB.
- Drop outgoing server-request `tracestate` values larger than 512 bytes while preserving `traceparent`.
## Testing
Add regression coverage for oversized requests across transports, malformed messages, requests wrapped in `RawValue`, fragmented Noise messages, large responses, streamed notifications, and trace-context preservation.
GitOrigin-RevId: 3fb87b6dc334296985ef40983d7276acfe9b9796
## Why
An executor can renew its registration while retaining its Noise identity. Connection refresh must distinguish registrations even when their keys match, and a stale registry lookup must not retire a session that recovery has already renewed.
## What changed
- Track the installed session's registration and expose it through `Environment::cached_executor_registration_id()` without connecting. Publish registration changes only when the new connection is installed.
- Require both registration and executor key to match before refresh reuses a session. Retry stale lookups and prevent recovery from installing a connection after retirement.
- Pin session recovery to the original Noise key while allowing registration renewal to preserve the session and running processes.
- Expose `CodexThread::active_turn_environment_selections()` so hosts can authorize steering against the active turn's selections, including starting and failed environments, independently of later settings updates.
## Testing
Add regression coverage for registration replacement, renewal with a running process, stale refresh lookups, missing sessions, and rejection of changed Noise keys. Extend active-turn tests to check selection snapshots across settings updates, deferred startup, and turn completion.
GitOrigin-RevId: 4395bc6c57f137691887e6585f52dcc5b958101c
## Why
Allow remote exec servers to connect directly to AWS-hosted registries that
authenticate registry requests and WebSocket handshakes with AWS SigV4.
## What changed
- Add `--remote-transport direct` with SigV4 profile, region, and service
options while keeping Noise as the default transport.
- Register the `direct_jsonrpc_v1` transport and carry plain exec-server
JSON-RPC messages over the authenticated WebSocket.
- Reuse direct registrations across transient disconnects, refresh them after
a `409 Conflict`, and require TLS for non-loopback endpoints.
## Testing
- Cover CLI validation and SigV4 request signing.
- Exercise direct registration, handshake retry behavior, JSON-RPC
interoperability, and process recovery after reconnecting.
GitOrigin-RevId: 0755df330ba3abe5db0a516fdaa49338d9bbe2d2
## What changed
- Wait for the authenticated Noise handshake before sending the JSON-RPC
`initialize` request.
- Share the configured initialization timeout across the handshake and RPC,
while preserving the configured duration in timeout diagnostics.
- Record the handshake and initialization RPC as child spans of the existing
initialization span, and terminate transport tasks when handshake readiness
fails.
## Testing
- Cover stalled handshake timeouts and cleanup of the underlying WebSocket.
- Cover timeout reporting after the handshake has consumed part of the budget.
GitOrigin-RevId: c3eb5f7cfedd8d155fde5b7614fc2eb399ce337c
## What changed
- Add `RemoteEnvironmentOptions` and `upsert_environment_with_options` so embedding hosts can attach trusted HTTP headers to remote exec-server WebSocket handshakes.
- Preserve the headers across session reconnects while redacting them from debug output.
- Reject invalid, duplicate, and WebSocket-controlled headers, and require `wss://` for non-loopback destinations.
- Connect header-bearing loopback WebSockets directly, with DNS results restricted to loopback addresses.
## Testing
- Cover header validation and redaction, delivery on initial connections and reconnects, and loopback destination enforcement.
GitOrigin-RevId: f2e2b0456c8e33c13fd8ab4ee040a8b5c15e885d
## What changed
- Allow a valid Ready report to replace a deferred environment's provisioning failure while preserving the same environment instance.
- Treat provisioning failures as retryable and reconnect when a completed or concurrently publishing startup attempt contains a stale failure.
- Report provisioning failures from `status()` without starting a connection, and retain Ready state when a late failure arrives.
## Testing
- Cover recovery after failed and invalid provisioning reports, capability-root resolution after recovery, and the race between Ready and publication of an earlier startup failure.
GitOrigin-RevId: cce419403248168da81bc5a6ab20f919aed81142
## Why
Planned executor replacement needs a fresh session without waiting for the old
session's transient-disconnect recovery to finish.
## What changed
- Add `Environment::refresh_connection` for remote Noise registry-backed
environments. It performs a fresh registry lookup, reuses a healthy session
when the executor identity is unchanged, and connects to a replacement when
it has changed.
- Retire superseded sessions and connection attempts so they cannot publish
stale state, accept late RPC results, or replay outstanding work.
- Preserve the existing environment and filesystem handles while replacing the
underlying client, and require a live status probe before refresh succeeds.
## Testing
Add coverage for replacement and session reuse, recovery and connection races,
lookup and handshake failures, handle preservation, and late RPC responses.
GitOrigin-RevId: f1d11208cbfe8af8feb25f6b6b8100da82169a99
## Why
Refreshing a Noise rendezvous bundle after an unauthorized handshake inherited
the initial registry lookup's retry count. If that lookup had exhausted its
retries, a transient registry error during the refresh failed immediately.
## What changed
Reset the registry retry count along with the operation deadline before
requesting a replacement bundle after a `401` handshake response.
## Testing
Extend the initial Noise connection test to exhaust the first lookup's retry
budget, receive a `401`, then verify that an offline replacement lookup is
retried and connects successfully.
GitOrigin-RevId: bcfad50ea32d69855f1c2c4c461022b3ecbbd941
## What changed
- Add `EnvironmentManager::from_accepted_websocket` so embedding hosts can
construct a remote environment from an already accepted and authenticated
Axum WebSocket.
- Add `replace_accepted_websocket` to retire the current transport and resume
the same exec-server session on a host-supplied replacement connection.
- Serialize replacement handoffs, reject overlapping replacements, and release
the handoff claim when a replacement attempt is cancelled or fails.
## Testing
- Cover initial connection validation and immediate environment readiness.
- Verify replacement retry behavior and recovery of a running process and its
output after reconnecting.
GitOrigin-RevId: 1f2ab7bcf7b5abbbece5c101801432dc84a8058d
## What changed
- Retry transient environment-registry failures while opening the initial Noise rendezvous connection, including timeouts, interrupted response bodies, retryable HTTP statuses, and temporarily offline environments.
- Bound registry attempts with per-request and overall timeouts, exponential backoff, and a retry limit while returning permanent registry errors immediately.
- Preserve the existing one-time bundle refresh after an unauthorized WebSocket handshake, with a fresh registry retry window.
- Treat malformed complete JSON responses as terminal rather than retryable transport failures.
## Testing
- Cover transient and permanent registry responses, stalled requests, retry limits and deadlines, interrupted bodies, malformed JSON, unauthorized bundle refresh, and session-resume behavior.
GitOrigin-RevId: 5929274103702ad477e7a638ab489319a43a93fa
## What changed
- Add `x-cluster-name` from `OPENAI_CLUSTER` and
`x-openai-internal-caller` from `DD_SERVICE` to rendezvous WebSocket
handshakes when the environment values are nonempty and valid HTTP header
values.
- Apply the rendezvous headers to both client and remote executor connections
while preserving the existing W3C trace context headers.
GitOrigin-RevId: fead0244ba24fa208990b92d2170a9ae76ff2ceb
## Why
Model-reachable child processes should not inherit Codex launch context.
## What changed
- Treat `OPENAI_FEDERATION_RULE_ID` and `OPENAI_IDENTITY_TOKEN_FILE` as non-inheritable environment variables, with case-insensitive matching.
- Remove them after shell environment policy overrides and before spawning commands across execution, MCP, hooks, Git helpers, and remote helper processes.
## Testing
- Cover inherited and explicitly configured variants, including mixed-case names.
- Verify the variables are absent from real child environments and app-server command and process execution.
GitOrigin-RevId: 2535527893985fef0995617f4c5b2462bea7c136
## What changed
- Add pending, ready, and failed provisioning states for Noise environments.
- Preserve the same environment instance whether provisioning is reported before or after materialization, and reject conflicts with ordinary environments.
- Make readiness and failure reports idempotent while rejecting contradictory terminal transitions.
- Delay connection attempts until a provisioned environment is selected and provisioning succeeds.
## Testing
- Cover status reports before and after materialization, terminal failures, repeated and contradictory reports, and replacement between ordinary and deferred environments.
GitOrigin-RevId: 4360a8f2a80c1a99a1dc9257e5d77c07b72b8eb3
## What changed
- Preserve the current tracing span and subscriber when lazy remote environment
startup runs in a background task.
- Add spans for the remote connection, environment registry, Noise, and
rendezvous WebSocket stages, including relevant environment and registration
identifiers.
- Propagate the current trace context to environment registry connection and
harness-key validation requests.
GitOrigin-RevId: 0044c3eb94d59c6a6b92752d3d372195be4adb81
## Why
Remote environment connections need to honor Codex's effective outbound proxy policy, including when a rendezvous connection reconnects.
## What changed
- Pass the configured `HttpClientFactory` into remote environment transports and use `WebSocketConnector` for exec-server and rendezvous WebSockets.
- Resolve proxy routes asynchronously so these connections can use the configured system proxy.
- Add connector options that preserve Tungstenite's default TLS behavior and enable `TCP_NODELAY` for latency-sensitive rendezvous traffic.
## Testing
- Verify prepared remote environments connect through a configured system proxy.
- Verify initial and reconnected encrypted relay peers use the system proxy.
- Cover default TLS selection and opt-in `TCP_NODELAY` behavior in the WebSocket client.
GitOrigin-RevId: 8a8da2116e37cb3a891269d0c0b037986fecdd3c
## What changed
- Replace pending WebSocket URL registration with deferred Noise environment
registration that gates connection attempts on an explicit readiness signal.
- Reuse the Noise rendezvous transport after readiness so reconnects request a
fresh connection bundle.
- Preserve terminal errors for failed or dropped registrations and keep late
completion isolated from replacement environments.
## Testing
- Add coverage for readiness gating, registration failure and replacement,
eager Noise connections, and reconnection through a fresh rendezvous bundle.
GitOrigin-RevId: 83e23fa03a02e3b2bdf1a83fe26d7ac461f55cf3
## 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
## Summary
Disable Nagle unconditionally for both exec-server Rendezvous WebSocket
connections.
- pass `disable_nagle=true` at the executor and harness connection call
sites
- keep the existing signed URL, protocol, and connection flow unchanged
- add no feature flag, rollout schema, path variant, or
experiment-specific telemetry
The companion internal PR enables `TCP_NODELAY` on accepted Rendezvous
sockets: https://github.com/openai/openai/pull/1082463
## Why
Rendezvous carries small, latency-sensitive relay and JSON-RPC frames.
Three staging runs of 30 steady-state `process/read` calls per
configuration measured p50 improving from 139.1 ms to 81.5 ms and p95
from 162.0 ms to 95.8 ms with Nagle disabled.
The expected packet overhead is small at the current connection scale.
We will use existing latency, error, packet, and CPU monitoring and
revert normally if production regresses.
## Rollout and rollback
The client and accepted-socket changes can deploy independently. New
connections receive the setting as each side deploys. Rollback is a
normal code revert; there is no persisted assignment or gate state to
unwind.
## Validation
- `just test -p codex-exec-server --lib`: 164 passed
- `just fix -p codex-exec-server`: passed
- `just fmt`: passed
- independent final review found no actionable issue
## Summary
- Record bounded duration and outcome metrics for remote environment
registration and Noise rendezvous connection attempts.
- Count reconnects by bounded reason: disconnect, connection failure, or
rejected registration.
- Trace registration at the owning client boundary without exporting raw
environment or registration identifiers.
- Replace the stale pre-Noise WebSocket observability design with the
current remote transport model.
## Stack
Review and land this stack in order:
1. #27466 — trace exec-server JSON-RPC requests
2. #27467 — record bounded connection, request, and process lifecycle
metrics
3. #27470 — observe remote registration and Noise rendezvous lifecycle
**(this PR)**
## Validation
- `just test -p codex-exec-server --lib` (149 passed)
- `just test -p codex-cli --test exec_server` (4 passed)
- `just argument-comment-lint`
- `just bazel-lock-check`
- `just fix -p codex-exec-server -p codex-cli`
- `just fmt`
## Summary
- add a provider API that supplies a fresh signed WebSocket URL for each
remote exec-server connection
- refresh the signed URL after disconnects and retry once when a
handshake returns `401 Unauthorized`
- allow `EnvironmentManager` consumers to register remote environments
backed by the URL provider
## Tests
- `just test -p codex-exec-server -E
'test(remote_websocket_client_refreshes_url_after_unauthorized_handshake)
| test(remote_websocket_client_refreshes_url_after_disconnect)'` — 2
passed
- `cargo check -p codex-core-api` — passed
- `just fix -p codex-exec-server` — passed
- `just fix -p codex-core-api` — no test targets; no-op
- `just fmt` — passed
- `just test -p codex-exec-server` — 187 passed; 32 unrelated macOS
sandbox tests could not invoke nested `sandbox-exec` (`Operation not
permitted`)
Supersedes #28288 (closed).
## Why
A short WebSocket interruption currently ends every client-side process
handle, even though exec-server keeps the server session and its
processes alive for a short time.
This is especially visible for executor-backed stdio MCP servers: a
temporary connection loss becomes a permanent `Transport closed` error.
The server already has the information needed to resume the session, but
the client opens a fresh session instead of using it.
This change reconnects below the process and MCP layers. Existing
process handles stay valid, missed output is recovered, and the same
server-side processes continue running.
## State machine
One logical `ExecServerClient` stays alive while its underlying RPC
connection changes generations.
```text
transport closes
+------------------------------------------------+
| v
+-------------+ +-------------+
| Connected | | Recovering |
+-------------+ +-------------+
^ |
| session resumed, processes caught up | retryable error
+------------------------------------------------+ loops until deadline
|
| deadline or permanent error
v
+-------------+
| Failed |
+-------------+
```
### `Connected`
- New RPC calls use the current connection.
- Process notifications are published in sequence order.
- A disconnect only starts recovery if it came from the current
connection generation. Late events from older generations cannot replace
the active connection.
### `Recovering`
- New calls wait instead of choosing a half-connected RPC client.
- Existing process handles, wake subscriptions, and event subscriptions
stay open.
- Streaming HTTP response bodies fail immediately because their byte
streams cannot be resumed safely.
- Recovery first waits for process starts that were already in flight. A
start whose result became ambiguous is cleaned up after reconnection
instead of being silently adopted.
- The client reconnects with the learned `session_id`. The server may
briefly report that the old connection is still attached, so that error
is retried until the detach finishes.
- The notification consumer starts before the resume handshake
completes. This prevents a busy process from filling the notification
queue and blocking the initialize response.
- Before installing the new connection, the client catches up every
recoverable process with `process/read`.
### `Failed`
- Recovery stops after 25 seconds or after a permanent error.
- Waiting calls are released with one stable disconnect error.
- Existing process sessions receive a terminal failure instead of
waiting forever.
## Recovering process events
Output, exit, and close events share one sequence. During normal
operation, the client buffers early events until every lower sequence
has been published.
After reconnection, the client reads each process starting after its
last published sequence:
1. Retained output chunks are inserted by sequence number.
2. Exit and close state are reconstructed in their sequence positions.
3. Events already received as live notifications are ignored as
duplicates.
4. Newly contiguous events are published in order.
5. If the server no longer retains enough output to fill a sequence gap,
only that process is terminated and failed. The recovered connection
remains usable for other processes.
The server reports its full next event sequence for unbounded reads,
including exit and close events. Closed processes remain readable for
the same 30-second window used to retain detached sessions.
## Other details
- Detached server sessions are retained for 30 seconds, leaving margin
around the client's 25-second recovery deadline.
- Session attach and detach update the active notification sender under
the same attachment lock, so an old connection cannot clear a newly
attached sender.
- A dedicated error code distinguishes the temporary "session is still
attached" race from permanent initialization errors.
- Process starts are identity-checked on both client and server. Cleanup
from an older start cannot remove a newer process that reused the same
ID.
- Mutating requests that were already in flight when the transport
closed are not replayed, because the client cannot know whether the
server applied them. Requests started after recovery is known wait for
the replacement connection.
- We assume the server/client version stays in sync (on the before/after
this PR)
## User impact
Long-running commands and stdio MCP servers can survive a temporary
exec-server WebSocket interruption without changing process IDs or
losing output produced during the outage.
## Why
Rendezvous forwards traffic between the orchestrator and exec-server.
The endpoints need to authenticate each other and encrypt that traffic
without trusting Rendezvous with plaintext or endpoint keys.
## Changes
- Adds a hybrid Noise IK channel through Clatter using X25519,
ML-KEM-768, AES-256-GCM, and SHA-256.
- Binds each handshake to `environment_id`, `executor_registration_id`,
and `stream_id`.
- Pins the registry-provided executor key and carries the harness
authorization inside the encrypted handshake.
- Orders relay frames before consuming Noise nonces and fragments large
JSON-RPC messages into bounded records.
- Bounds handshake payloads, frames, streams, and message reassembly.
Runtime activation is in
[openai/codex#26245](https://github.com/openai/codex/pull/26245).
## Stack
1. **[openai/codex#26242](https://github.com/openai/codex/pull/26242)**:
Noise channel and relay transport
2. [openai/codex#26245](https://github.com/openai/codex/pull/26245):
remote registration and runtime activation
## Verification
- `just test -p codex-exec-server`
- Oversized initiator payload regression coverage
- `just fix -p codex-exec-server`
- `just bazel-lock-check`
- `cargo shear`
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
Remote exec-server now needs one executor websocket to serve multiple
harness JSON-RPC sessions. Rendezvous routes by `stream_id`, and the
exec-server side needs to use the same stable relay frame contract
instead of a hand-rolled JSON shape.
The relay protocol also needs to make ownership boundaries clear:
harness and executor endpoints own sequencing, acks, retries, duplicate
suppression, segmentation, and reassembly; rendezvous only routes
frames.
## What Changed
- Add the checked-in `codex.exec_server.relay.v1.RelayMessageFrame`
proto plus generated prost bindings for `codex-exec-server`.
- Encode remote harness/executor relay traffic as binary protobuf
websocket frames while keeping local websocket JSON-RPC unchanged.
- Demux executor-side relay streams into independent
`ConnectionProcessor` sessions keyed by `stream_id`.
- Add a programmatic `RemoteExecutorConfig::with_bearer_token(...)`
constructor for non-CLI callers and integration tests.
- Add an integration test that starts the remote executor against a fake
registry/rendezvous websocket and verifies two virtual streams share one
executor websocket without cross-talk, including per-stream reset
behavior.
- Document the remote relay envelope, sequence ranges, `ack`/`ack_bits`,
and endpoint responsibilities in `exec-server/README.md`.
## Verification
- `cargo test -p codex-exec-server --test relay
multiplexed_remote_executor_routes_independent_virtual_streams --
--exact`
- `cargo test -p codex-exec-server --test relay`
- `cargo test -p codex-exec-server` passed outside the sandbox. The
sandboxed run hit macOS `sandbox-exec: sandbox_apply: Operation not
permitted` in filesystem sandbox tests.
## Why
`codex exec-server` should keep the existing public `ws://IP:PORT` URL
shape while serving that websocket connection through an HTTP upgrade
path internally. That keeps the client-facing configuration simple and
allows the listener to work through intermediate HTTP-aware
infrastructure.
## What changed
- keep the emitted and configured exec-server URL as `ws://IP:PORT`
- serve that websocket endpoint through Axum HTTP upgrade handling on
`/`
- expose `GET /readyz` from the same listener for readiness checks
- route upgraded Axum websocket streams through the shared JSON-RPC
connection machinery
- initialize the rustls crypto provider before websocket client
connections
- preserve inbound binary websocket JSON-RPC parsing for compatibility
with the prior transport behavior
## Verification
- `cargo test -p codex-exec-server --test health --test process --test
websocket --test initialize --test exec_process`
## Why
The environment-backed exec-server transport currently hardcodes 5
second connect and initialize timeouts in `client_transport.rs`. That is
short for SSH-backed stdio environments and remote websocket
environments, and there is currently no way to raise those values from
`CODEX_HOME/environments.toml`.
This stacked follow-up raises the default environment transport timeouts
and lets each configured environment override them in
`environments.toml`.
## What Changed
- raise the default environment transport connect and initialize
timeouts from 5s to 10s
- store concrete timeout values on `ExecServerTransportParams` instead
of hardcoding them in `connect_for_transport(...)`
- add `connect_timeout_sec` and `initialize_timeout_sec` to
`[[environments]]` entries in `environments.toml`
- apply parse-time defaults so runtime transport code receives fully
resolved timeout values
- reject `connect_timeout_sec` on stdio environments because it only
applies to websocket transports
- extend parser tests to cover the new fields and defaults
## Stack
- base: https://github.com/openai/codex/pull/21794
- this PR: configurable environment transport timeouts
## Validation
- `cd
/Users/starr/code/codex-worktrees/exec-env-timeouts-config-20260508/codex-rs
&& just fmt`
- not run: tests
---------
Co-authored-by: Codex <noreply@openai.com>
## Why
Configured environments need to connect to exec-server instances that
are not necessarily already listening on a websocket URL. A
command-backed stdio transport lets Codex start an exec-server process,
speak JSON-RPC over its stdio streams, and clean up that child process
with the client lifetime.
**Stack position:** this is PR 2 of 5. It builds on the server-side
stdio listener from PR 1 and provides the client transport used by later
environment/config PRs.
## What Changed
- Add `ExecServerTransport` variants for websocket URLs and stdio shell
commands.
- Add stdio command connection support for `ExecServerClient`.
- Move websocket/stdio transport setup into `client_transport.rs` so
`client.rs` stays focused on shared JSON-RPC client, session, HTTP, and
notification behavior.
- Tie stdio child process cleanup to the JSON-RPC connection lifetime
with a RAII lifetime guard.
- Keep existing websocket environment behavior by adapting URL-backed
remotes to `ExecServerTransport::WebSocketUrl`.
## Stack
- 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server
listener
- **2. This PR:** https://github.com/openai/codex/pull/20664 - Add stdio
exec-server client transport
- 3. https://github.com/openai/codex/pull/20665 - Make environment
providers own default selection
- 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME
environments TOML provider
- 5. https://github.com/openai/codex/pull/20667 - Load configured
environments from CODEX_HOME
Split from original draft: https://github.com/openai/codex/pull/20508
## Validation
Not run locally; this was split out of the original draft stack and then
refactored to separate transport setup from the base client.
---------
Co-authored-by: Codex <noreply@openai.com>