## Why
`EnvironmentConfig.network_policy` could describe attachment-owned traffic
restrictions, but core rejected every configured policy because execution did
not enforce it.
## What changed
- Resolve each remote environment's network policy for the selected command
and apply it to the execution-scoped proxy.
- Compose owner rules with controller constraints and saved network decisions
while preserving inherited domain and Unix-socket denials.
- Keep strict allowlists non-expandable, allow reviewable policies to use
network approvals, and reject sandbox escalation that would bypass an owner
policy.
- Reject policies for local execution, disabled managed enforcement, or a
disabled controller proxy.
## Testing
Added coverage for policy composition, scoped remote proxy behavior, approval
and denial flows, offline execution, and unsupported environment authority.
GitOrigin-RevId: d9331f616df24de6cd13ed68196f0ff7b0ca4dd9
## 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
- Use `exec_command` and `write_stdin` as the shell tool surface.
- Treat legacy `default`, `local`, and `shell_command` model metadata as `unified_exec`.
- Remove obsolete shell-selection configuration and runtime paths while preserving the feature and policy gates for zsh fork execution.
GitOrigin-RevId: d743cbe598630d73052f1fecad680c4cde17977d
## What changed
- Remove the legacy `shell_command` handler and runtime, leaving `exec_command`
and `write_stdin` as the shell execution tools.
- Treat legacy `shell_command` model metadata as `unified_exec`, and normalize
legacy user opt-outs so they do not disable command execution. Managed feature
requirements and `shell_tool` can still disable it.
- Preserve shell approvals, sandboxing, zsh-fork support, and output truncation
through the unified execution path.
## Testing
- Cover legacy configuration and model-metadata compatibility.
- Exercise unified shell execution, approvals, truncation, and `apply_patch`
serialization across the app-server and core test suites.
GitOrigin-RevId: 5c2fd6164fc3519cdae4944cb9db276b8467311c
## Why
Resumed threads can carry a persisted capability-root location that no longer
matches the location reported by the attached executor.
## What changed
- Refresh matching thread-owned capability roots from the live environment while
preserving persisted roots when the executor reports none.
- Use the same merge behavior for capability-root inspection and per-step MCP
resolution, without changing owner-configured root precedence.
## Testing
Extend the remote-environment resume test to cover both an executor-provided
replacement root and the persisted-root fallback.
GitOrigin-RevId: 0c66195b2406f431a2e156e00d5225a8d22d18b2
## Why
An `apply_patch` path can be replaced with a symlink after verification, allowing an unsandboxed patch operation to reach a different file than the one that was approved.
## What changed
- Add `follow_symlinks` options to executor filesystem reads, writes, metadata lookups, directory creation, and removal, including the corresponding `followSymlinks` protocol fields.
- Implement no-follow filesystem operations on Unix and Windows that reject links in any path component and restrict file access to regular files.
- Run `apply_patch` with symlink traversal disabled when an otherwise-required sandbox is bypassed, while retaining the existing follow-symlink default for standalone callers.
## Testing
- Cover leaf and ancestor symlinks across patch add, update, delete, and move operations, including a path swap after verification.
- Exercise local and remote no-follow filesystem behavior, concurrent directory creation, special-file rejection, and Windows reparse points.
GitOrigin-RevId: 43fd479084891493ce13564fbd894b98f329c6dd
## Why
Environment-owned permission profiles and workspace roots must remain effective
without being persisted or restored as thread-owned settings.
## What changed
- Keep thread settings snapshots and restore data separate from the effective
configuration supplied by the primary environment.
- Use resolved environment permissions, active profiles, workspace roots, and
network policy when building turn context and session events.
- Preserve environment selections when agents are evicted, resumed, or spawned,
and validate owner configuration when it becomes ready.
- Start memory initialization only after the primary environment is configured.
## Testing
- Extend remote-environment and multi-agent tests to cover settings persistence,
owner permission profiles, workspace roots, spawning, eviction, and resume.
GitOrigin-RevId: d82f126981cd0190233020bbdd61d96c70558868
## What changed
- Add environment-provided MCP restrictions for configured and plugin-provided servers.
- Disable attachment-scoped servers while their environment configuration is pending, failed, or unselected, while preserving selected-plugin access and the controller-owned Apps server.
- Apply the resolved policy to runtime startup, model tool exposure, telemetry, OAuth flows, and skill dependency installation.
## Testing
- Cover policy filtering for configured and plugin servers, environment state transitions, Apps ownership, and managed OAuth rejection.
GitOrigin-RevId: 7ca5d4dda14068e758497f88835d5cba685e159e
## Why
Thread settings could replace an owner-provided environment configuration with
`FromThread`, allowing the thread to take ownership of that configuration.
## What changed
Reject environment settings updates that change an existing owner-provided
configuration to `FromThread`.
## Testing
Cover preview and turn settings updates for pending, ready, and failed
owner-provided environments.
GitOrigin-RevId: c516954819447ceb29e2ff9f29fe781ca101ae54
## Why
Filesystem permission paths can use a convention that differs from the host
running Codex. Converting them immediately to native absolute paths can change
the meaning of ambiguous paths such as `/C:/secret` or Windows UNC paths.
## What changed
- Store literal filesystem permission paths as `PathUri` values through the
runtime policy and execution protocol.
- Keep legacy string-based serialization at explicit protocol boundaries and
reject conversions that cannot be represented losslessly.
- Encode native paths as opaque URIs when a normal file URI would imply the
wrong path convention.
## Testing
Added coverage for cross-platform and ambiguous path round trips, UNC path
variants, permission-profile serialization, and deny-policy enforcement.
GitOrigin-RevId: 5247713796d1f2bb4e02f94eb9fc82d4698060f0
## What changed
- Add `EnvironmentNetworkPolicy` as a serializable view of portable domain,
Unix-socket, upstream-proxy, and local-binding restrictions without exposing
controller-owned proxy runtime settings.
- Add an optional `network_policy` field to `EnvironmentConfig` and re-export
the policy and related permission types through the core APIs.
- Reject owner-provided network policies during environment preview and
readiness until runtime enforcement is implemented, while preserving the
existing controller policy when the field is absent.
## Testing
- Cover rejection through both environment preview and readiness, including
preservation of the existing environment selection.
GitOrigin-RevId: 80ad4cf4d4a45632daa7a823e6cf568eb0e8bb80
## What changed
- Add an optional restrictive execution policy to `EnvironmentConfig` and merge it over the active command policy for shell execution and approval decisions.
- Reject environment policies that contain allow rules, so environment configuration can only tighten command access.
- Include the environment policy fingerprint in cached approval keys so policy changes require fresh session approvals.
## Testing
- Cover restrictions overriding saved prefix approvals, policy changes invalidating session approvals, and environment overlays remaining effective when model-specific prefix filtering is active.
GitOrigin-RevId: e94f877549ed5e0f577b8cb52f3c1872c58f029d
## What changed
- Carry `ShellEnvironmentPolicy` in each resolved `EnvironmentConfig` and use
the selected turn environment's policy for shell commands, user shell tasks,
and unified exec.
- Infer the policy from the thread configuration when an environment does not
provide its own resolved configuration.
- Redact the policy from `EnvironmentConfig` debug output because it can contain
explicit environment variable values.
## Testing
- Verify shell handlers and unified exec filter inherited variables according
to the selected environment while preserving its explicit overrides.
GitOrigin-RevId: 9f6a52aa4af60e5f17251a0e1b11e1926b779055
## Why
An environment connection can be available before its owner has supplied the
configuration for a particular thread attachment. Threads need to start without
blocking while ensuring turns do not use that attachment prematurely.
## What changed
- Accept `Pending` environment configuration and resolve each attachment only
after both its shared executor connection and owner configuration are ready.
- Add a `Failed` configuration state and `environment_failed` callback so an
owner can fail one thread's attachment without affecting other threads.
- Keep pending and failed attachments out of capability-root inspection and turn
environments, and allow failed attachments to recover through a ready update.
- Apply owner configuration before waking a waiting turn so its permission
profile, login-shell policy, capability roots, and tools are immediately
consistent.
## Testing
Add an integration test covering non-blocking thread startup, independent ready
and failed callbacks, waiting-turn resumption, installed capability and tool
configuration, and recovery from failure.
GitOrigin-RevId: d587e2025d584c867d782d470b18bf5a1a27b76c
## Why
Environment attachments can either inherit configuration from their thread or
provide their own. Later thread setting updates must refresh inherited
configuration without overwriting attachment-owned permissions and capability
roots.
## What changed
- Resolve each attachment's configuration when it is selected and retain
whether it came from the thread or the attachment owner.
- Apply subsequent thread configuration updates only to thread-owned
attachments, while preserving ownership across snapshots and child threads.
- Keep the resolved configuration with the environment selection so runtime
consumers use a single canonical value.
## Testing
- Extend remote-environment coverage to verify that thread-owned permissions
follow thread updates while owner-provided read-only permissions remain in
effect.
- Cover configuration inheritance, attachment replacement, and owner-configured
capability roots.
GitOrigin-RevId: fe70c4be5f151432b69bf4b141e316faa89036ae
## What changed
- Add a resolved `permission_profile` to each `EnvironmentConfig` and use the
complete attachment config for execution and capability-root selection.
- Let `Ready` environment configurations override thread permissions while
`FromThread` selections continue to inherit them.
- Restrict inherited Guardian environment profiles to read-only permissions.
## Testing
- Add coverage proving that a read-only environment blocks writes even when
the thread permits workspace writes.
- Update environment inheritance, Guardian review, and capability-root tests
for the resolved attachment configuration.
GitOrigin-RevId: 1a313b9e4892b1a579a0e880e322a782b4f6c0a7
## What changed
- Add `EnvironmentConfigState` to `TurnEnvironmentSelection` so each environment attachment can inherit thread configuration or carry ready configuration for its login-shell policy and selected capability roots.
- Apply ready configuration during thread creation and thread-settings updates while reusing the existing environment connection.
- Validate selection configuration before applying settings, rejecting unsupported pending state and invalid capability roots.
## Testing
- Cover attachment-scoped capability roots, ready configuration at thread startup and turn updates, and rejection of invalid selections without changing existing settings.
GitOrigin-RevId: 86cf8f2148fdaa746ea0dd12890ab7b1577d7587
## What changed
- Make `ThreadEnvironments` the source of truth for live environment selections and use it when building configuration snapshots, permission profiles, per-turn configuration, and MCP refresh inputs.
- Keep settings previews side-effect free while applying accepted environment updates consistently to subsequent turns.
- Move `EnvironmentConfig` into `codex-protocol` so environment ownership no longer depends on a core-only type.
## Testing
- Cover environment previews and snapshots, accepted and rejected steer updates, and active-turn environment stability.
GitOrigin-RevId: 7bcf8fb5df1215f720e4e9c419541a51a871aa5e
## What changed
- Add `TurnInputRequest` and typed submission results for atomically starting a turn, steering the active turn, or declining input with a specific reason.
- Expose `start_or_steer_turn`, `start_turn_if_idle`, and `steer_turn` on `CodexThread`, and migrate Core consumers to these APIs.
- Make app-server `turn/start` steer an active regular turn and return that turn's ID. Reject incompatible output schemas and non-steerable turns without applying settings or enqueueing input.
## Testing
- Cover concurrent start-or-steer submissions, accepted and rejected settings updates, output-schema compatibility, idle-start rejection, and app-server steering.
GitOrigin-RevId: dd9b5528d76ec650c019e97af420bc13190ea86a
## Why
World-state snapshots and merge patches are keyed collections of sections. Allowing
their persisted `state` field to contain any JSON value required replay code to
handle shapes that cannot represent world state.
## What changed
- Type `WorldStateItem.state` and snapshot patches as JSON object maps.
- Reconstruct full snapshots directly from those maps and apply patches without
fallible arbitrary-value conversions.
- Regenerate the app-server protocol schema for the object-only representation.
GitOrigin-RevId: 3d7662b74f3dd1f3ccc5fdd0200074c13a73cbe5
## Why
Threads that share an executor environment can require different capability roots and login-shell policies. Treating readiness configuration as executor-wide can expose one thread's settings to another.
## What changed
- Add `CodexThread::environment_ready` and `EnvironmentConfig` so hosts can install capability roots and login-shell policy for an exact thread, environment, and working-directory attachment.
- Validate attachment readiness updates, merge ready attachment roots with persisted thread roots, and refresh MCP capabilities when those roots change.
- Preserve installed environment policy across thread settings updates and child-thread inheritance while resetting it when the selected working directory changes.
## Testing
- Cover attachment config inheritance and reset behavior.
- Verify that threads sharing an executor retain independent model-visible capability roots and login-shell tool schemas across updates.
GitOrigin-RevId: 60e7a296b3e41b835b0413aa2837451e8a55c230
## What changed
- Add `codex-history` for model-history and persisted-rollout domain types, including `RolloutItem`, `RolloutLine`, `CompactedItem`, and initial/resumed history state.
- Re-export the persisted types from `codex-rollout` and update consumers to use the new crate boundary instead of `codex-protocol`.
- Preserve existing rollout serialization, including legacy numeric compacted-window IDs.
## Testing
- Add `codex-history` tests for rollout JSON round trips, compacted-history compatibility, persisted history modes, and multi-agent version selection.
GitOrigin-RevId: 944daa9297ddd231d3aebbdcb05fff4adf8b4e1b
## Why
Deferred environments can become ready after a turn starts. Guardian approval
reviews must use the environment selected for the current step so their working
directory and permission context are not taken from the stale turn snapshot.
## What changed
- Carry `StepContext` through tool execution and approval handling.
- Add a Guardian review context that pairs the parent turn with the current
step's environment snapshot.
- Use that snapshot for Guardian prompts, reviewer sessions, and delegated
reviewer threads while retaining turn-level fallbacks for callers without a
step context.
## Testing
Added an integration test that makes a deferred remote environment ready before
an escalated command and verifies Guardian receives that environment's working
directory and denied-read policy instead of the initial local environment's.
GitOrigin-RevId: 779096bc7d0e0c7453a009c02141029714b9b407
## What changed
- Add end-to-end coverage for a remote environment whose provisioning status is
reported ready before it is selected for a turn.
- Verify that `wait_for_environment` makes remote execution tools and selected
capability-root context available once the transport is ready.
- Update pending-environment and relay tests to cover separate materialization
and readiness reporting, including deferred connection and reconnection.
GitOrigin-RevId: d86d66e8f722e9bafac88b93d93e7c5244a7156e
## What changed
- Register the legacy `shell_command` handler only when exactly one local environment is available, including when unified exec keeps it hidden for compatibility.
- Reserve the unnamespaced `shell_command` name so external tools cannot claim it when the built-in handler is unavailable. Namespaced tools with that name remain supported.
## Testing
- Cover remote and multiple-environment tool plans, unified exec routing, and external tool registration for reserved and namespaced variants.
GitOrigin-RevId: 93834539b31ac8a7fea072bfa4de792bcc3e053b
## Why
With deferred execution, an environment can become ready before a later step
spawns a sub-agent. The child must inherit the environment selections available
to that step.
## What changed
- Use the current step context's environment selections when spawning agents in
both multi-agent implementations.
- Add coverage that waits for a remote environment to become ready, spawns an
agent, and verifies that the child receives both remote and local selections.
GitOrigin-RevId: 97c37d541342bbefc9217bf341a19ebdfead2c76
## Why
When a turn uses multiple environments, the environment context lists each one but did not identify which environment is primary.
## What changed
- Add a `primary` attribute to each environment in multi-environment model context.
- Persist primary selection in world-state snapshots and emit updates when the primary environment changes.
- Restate environments when transitioning between single- and multi-environment context, while preserving the existing single-environment format.
## Testing
- Cover primary-environment changes, single/multi-environment transitions, legacy snapshot compatibility, and deferred-executor promotion after environment startup.
GitOrigin-RevId: 73a1d794a227edef6e9b7844e7f947b7f598dc80
## What changed
- Add `WaitForEnvironmentToolConfig` as thread extension data for overriding the
model-visible tool and `environment_id` descriptions.
- Preserve the default descriptions when no override is provided or when the
configured descriptions exceed the input or serialized tool-spec limits.
- Keep `wait_for_environment` availability gated by the deferred executor
feature independently of whether a host override is present.
## Testing
- Cover default, custom, oversized, and feature-disabled tool configurations.
- Verify the custom descriptions in the deferred-environment integration flow.
GitOrigin-RevId: 6b49a73a434becd99ea5df911be53f3706a17c0a
## Why
Default read-only protections for project metadata should apply when paths such
as `.git`, `.agents`, and `.codex` exist, without causing sandbox setup to
materialize missing paths as ACL targets.
## What changed
- Add an optional `missing_path_behavior` to filesystem sandbox entries and
mark default project-metadata protections with `skip`.
- Preserve the behavior through permission transforms and exec/MCP protocol
serialization while keeping existing path wire variants stable.
- Ignore skip-missing entries when projecting configuration or Windows sandbox
overrides, while retaining explicit metadata carveouts.
## Testing
- Cover protocol round trips for path and special-path entries.
- Verify default metadata protections and Windows explicit carveout handling.
GitOrigin-RevId: 6df13dadacdd131c44aab9f15a967c81051355c1
## Why
Changes to the selected working directory can select different `AGENTS.md`
instructions. These updates need to reach the model even when the deferred
executor feature is disabled.
## What changed
- Refresh environment readiness and `AGENTS.md` before capturing each step.
- Record model-visible world-state changes for every session instead of gating
them on the deferred executor feature.
- Preserve the initial environment context without duplicating it.
## Testing
- Cover `AGENTS.md` replacement after a working-directory change.
- Verify that initial environment context remains unique with the deferred
executor both enabled and disabled.
GitOrigin-RevId: ea5dbe590cc049170259e7eccb6a3346fb2ed8e6
## Why
With deferred execution enabled, rebuilding a step context from the latest
thread environment snapshot could apply a settings update to a turn that was
already in progress.
## What changed
- Preserve each turn's captured environment selections when creating later
step contexts.
- Refresh only the readiness of environments that were already starting, while
retaining their original selection order.
- Apply updated environment selections to the next turn.
## Testing
Add coverage for changing the selected workspace while a turn is paused,
verifying that the active turn keeps its original working directory and the
next turn uses the new one.
GitOrigin-RevId: c6f4388c843384ef045b7abdbc303dcfca11d230
## Why
An execution environment marked `starting` is not yet usable, but unrelated work can continue while it becomes ready.
## What changed
- Add developer guidance that explains execution-environment isolation and the resources that become available after startup.
- Tell the model to wait only when the current task needs the starting environment.
- Retain the guidance across world-state updates without duplicating it in request history.
## Testing
- Cover world-state transitions, history retention, rollback handling, and deferred-environment request deduplication.
GitOrigin-RevId: f12f9204d4cbddce50a70eb0998beb6b66cd2fa9
## What changed
- Let deferred environments provide selected capability roots with their ready signal.
- Validate that those roots have unique, non-empty IDs, belong to the registering environment, and stay within the root limit.
- Include roots from ready turn environments when resolving MCP contributions, and refresh the MCP runtime when the selected root set changes.
- Expose the exact ready root set to MCP contributors so executor plugins become available with their environment.
## Testing
- Cover ready-root propagation, validation failures, replacement isolation, reconnection, and MCP plugin availability refresh.
GitOrigin-RevId: ec3498aab1164824025094e96a9b1063b7b731ad
## 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
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
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
## Why
With deferred executors, a turn can begin before a remote environment
attaches. AGENTS.md discovery previously ran only during session setup,
so instructions from a later environment never reached the model or the
session instruction sources.
WorldState persistence has now landed, so this uses the durable
model-visible baseline directly instead of carrying a temporary
resume/fork compatibility path.
## What
- Add an `AgentsMdManager` in `SessionServices` to own host
instructions, loaded state, and refresh caching.
- When `DeferredExecutor` is enabled, refresh AGENTS.md when attached
environment selections change and freeze the result in the corresponding
`StepContext`.
- Represent AGENTS.md as a persisted WorldState section for every
session, with bounded initial, replacement, and removal updates.
- Remove duplicate AGENTS.md state and rendering from
`SessionConfiguration` and `TurnContext`.
- Build initial context, per-request updates, and compaction context
from the same step-scoped value.
- On resume and fork, compare current instructions with the restored
WorldState baseline and inject a replacement exactly once when they
differ.
Builds on #29833, #29835, and #29837.
## Tests
- Covers a remote environment becoming ready mid-turn, with AGENTS.md
appearing on the next request exactly once and updating canonical
instruction sources.
- Covers full, unchanged, replaced, and removed AGENTS.md WorldState
rendering.
- Covers changed instructions across cold resume and fork without
duplicate reinjection.
- Covers remote-v2 compaction retaining creation-time instructions in
the live session and cold resume appending one replacement when the
source changed.
- Ran focused `codex-core` AGENTS.md, WorldState, and context-update
test suites.
## Why
`WorldState` currently remembers its model-visible diff baseline only in
memory. That leaves no durable source for restoring the exact baseline
after resume, fork, rollback, or compaction.
This is the second PR in the WorldState persistence stack, built on
#29833 and following #29249. It records durable state transitions; the
next PR will replay them during rollout reconstruction.
## What
- Add a `world_state` rollout item containing either a full snapshot or
an RFC 7386 JSON Merge Patch.
- Persist a full snapshot after initial context and after compaction
establishes a new context window.
- Persist non-empty patches when later sampling steps or turns advance
the WorldState baseline.
- Write model-visible history before its matching WorldState record, so
an interrupted write can only cause a safe repeated update on replay.
- Preserve WorldState records for full-history forks while excluding
them from thread previews, metadata, and app-server history
materialization.
Older binaries read rollout lines independently, so they skip the
unknown `world_state` records while retaining the rest of the thread.
## Testing
- `just test -p codex-core
snapshot_merge_patch_changes_and_removes_nested_values`
- `just test -p codex-core
world_state_baseline_deduplicates_until_history_is_replaced`
- `just test -p codex-core
deferred_executor_compaction_preserves_then_updates_environment_once`
- `just test -p codex-protocol`
- `just test -p codex-rollout`
- `just test -p codex-state`
- `just test -p codex-thread-store`
- `just test -p codex-app-server-protocol`
## Why
Start moving towards app-server tests defaulting to running against
remote & foreign OS executors. To do so we need a point of indirection
similar to core integration tests' `build_with_auto_env`, but with the
flexibility of letting tests control environment registration if they
need to.
## What
This adds:
- `TestAppServer::new_with_auto_env()` for constructing an app server
with a default environment defined by the test runner (e.g. bazel)
- `TestAppServer::auto_env_params()` for tests to easily acquire turn
env params tailored to the automatic environment
- `TestAppServer::send_thread_start_request_with_auto_env()` to make it
easy for tests to start a thread using the automatic environment
The above methods all fail if the test calling them has set up an
environment where the automatic environment configuration conflicts with
test-created state.
## Validation
Adds a couple of basic smoke tests to the app-server test suite.
Follow-ups will migrate more tests to use it.
## Why
With `DeferredExecutor`, a sampling request can begin while an
environment is still starting. The model can see that pending state, but
needs a way to wait for the environment within the same turn before
continuing.
Environment startup is owned by Core, so the wait tool should use the
same request-frozen `StepContext` that advertised the starting
environment. This keeps tool registration and execution tied to the
exact startup operation the model saw, even if live thread state later
changes.
Supersedes #29735.
## What
- register `wait_for_environment` when the current `StepContext`
contains starting environments
- wait on the selected `StartingTurnEnvironment` shared resolution and
return a bounded ready or failed result
- rebuild the next request normally, removing the wait tool and exposing
ready environment tools, or reporting the environment as unavailable
after failure
## Testing
- `just test -p codex-core deferred_executor_`
- verifies the wait tool is replaced by environment-backed tools after
startup
- verifies startup failure removes both the wait tool and unavailable
environment tools while notifying the model
## Why
Use a clearer name for what happens when this helper sets up a test
environment.
## What
- Rename the builder and its harness wrapper to use `auto_env` instead
of `remote_env` because the helper will set up a local environment if
configured by the build system.
## Why
Core tests should branch on the executor's operating system, not on
runner details such as Docker or Wine. This keeps platform behavior
stable as new test backends are added and reserves Wine-specific skips
for actual runner debt.
## What
- Add `TestTargetOs` and target/host-aware skip helpers while keeping
`TestEnvironment` internal.
- Replace topology enum access with remote predicates and a narrow
Docker accessor.
- Migrate OS-semantic Wine skips, preserve runner-specific gaps, and
document the skip taxonomy.
## Validation
- `just test -p core_test_support`
- `just test -p codex-core
remote_test_env_can_connect_and_use_filesystem`
- `bazel test //codex-rs/core:core-all-wine-exec-test
--test_output=errors` reached test execution; unrelated existing
view-image, path, and timing failures remain.
- `just test -p codex-core` and `just test` reached broad test
execution; this checkout has unrelated helper, sandbox, and timing
failures.
## Why
With deferred executors, an environment can become ready between two
sampling requests in the same turn. The model-visible environment
update, advertised tools, and eventual tool execution must all describe
the same request-time view.
Otherwise, a request built while only environment B is ready can
advertise a tool without an `environment_id`; if higher-priority
environment A becomes ready before execution, that call could silently
run in A instead.
This PR is stacked on #29527.
## Design
`run_turn` captures one `Arc<StepContext>` at each sampling-request
boundary. That step owns the request's `TurnContext` and environment
snapshot.
- World-state environment updates and tool planning borrow that same
step.
- `ToolCallRuntime` retains the `Arc` while asynchronous tool calls
execute.
- `ToolInvocation` carries the step to handlers; its temporary `turn`
compatibility field is derived from the same object.
- `ToolRouter` does not retain `StepContext`; it only uses it while
constructing the request's tool set.
- With `DeferredExecutor` disabled, step capture keeps using the
environments frozen at turn start.
Simply: every sampling request gets one consistent picture of its
environments, from what the model sees through where its tool calls run.
## What changed
- Build environment-dependent tool specs from the current request's
`StepContext`.
- Use that same step for unified exec, legacy shell, `apply_patch`,
`view_image`, and `request_permissions` execution.
- Hide environment-backed tools, including `request_permissions`, while
no environment is attached.
- Resolve legacy shell paths and metadata from the selected step
environment instead of the stale turn-start environment.
- Capture explicit steps at non-turn-loop boundaries such as compaction,
prompt debug, and startup prewarm.
- Reconcile prompt-debug history from the same step used to build its
tools.
## Follow-up
- Bind yielded code-mode cells to the tool runtime that created them, so
nested calls made after yielding continue to use the originating
request's `StepContext`.
## Test plan
- `just test -p codex-core
deferred_executor_updates_context_and_tools_after_startup`
- `just test -p codex-core
environment_count_controls_environment_backed_tools`
- `just test -p codex-core
build_prompt_input_includes_context_and_user_message`
## Why
Follow-up to #29249 and its [compaction review
thread](https://github.com/openai/codex/pull/29249#discussion_r3455055101).
During a turn, environment readiness can change between sampling
requests. Inline compaction must render the same model-visible
`WorldState` used by the request it follows. Rebuilding that state
during compaction can observe a newer environment, make replacement
history disagree with what the model saw, and suppress the next
environment update.
## What changed
- Make `run_turn` own the current `Arc<WorldState>` and replace it only
between sampling requests.
- Build each state from an explicitly chosen environment snapshot, diff
deferred-executor steps against the turn-owned state, and retain the
latest state in `ContextManager` only for cross-turn and resume
tracking.
- Pass the exact turn-owned state into inline compaction and explicit
new-context-window replacement.
- Carry that state with
`InitialContextInjection::BeforeLastUserMessage`, so replacement context
and its stored baseline cannot come from different snapshots.
- Remove obsolete state-recapture helpers and ambiguous TurnContext-only
WorldState builders.
- Add an integration test that moves an environment from starting to
ready during a paused turn, triggers compaction, and verifies the next
request receives the readiness update exactly once.
## Test plan
- `just test -p codex-core
deferred_executor_compaction_preserves_then_updates_environment_once`
- `just test -p codex-core process_compacted_history`
- `just test -p codex-core mid_turn_continuation_compaction`
- `just test -p codex-core build_initial_context`
- `just test -p codex-core
ignores_session_prefix_messages_when_truncating`
## Why
Downstream refactors are producing confusing code with this
functionality having a very generic name. Encoding the specific
conversion approach in the method name makes it clearer.
## What
Rename `PathUri::from_path` to `PathUri::from_host_native_path` and
update its Rust call sites.
## Why
#29113 moved remote sandbox setup and enforcement to the exec server.
That gives the executor ownership of the platform-specific work: a Linux
executor chooses and runs a Linux sandbox even when the Codex
orchestrator is running on macOS or Windows.
It also means the orchestrator no longer knows which concrete sandbox
the executor selected. When that sandbox blocks a remote command, the
orchestrator currently sees only a failed process and can treat the
denial as an ordinary command failure. The existing sandbox approval and
retry path is then skipped.
This PR lets the executor report one portable fact:
> This command probably failed because the executor sandbox blocked it.
The executor keeps its concrete sandbox type private. The protocol sends
only the semantic result.
## Example
Suppose a local macOS Codex session asks a Linux devbox to write outside
the allowed workspace.
Before this PR:
```text
Linux sandbox blocks the write
-> remote process exits with "Permission denied"
-> local orchestrator sees an ordinary command failure
-> the normal sandbox approval and retry path can be skipped
```
With this PR:
```text
Linux sandbox blocks the write
-> executor reports sandboxDenied: true
-> unified exec returns UnifiedExecError::SandboxDenied
-> the existing approval prompt is shown
-> an approved retry runs through the existing unsandboxed retry path
```
## What changes
### The executor remembers its selected sandbox
The prepared remote process now retains the executor-selected
`SandboxType`. This value never crosses the executor boundary.
Commands started without a sandbox retain `SandboxType::None` and are
never reported as sandbox denials.
### The executor uses the existing denial heuristic
The existing local denial heuristic moves from `codex-core` into the
shared `codex-sandboxing` crate.
When a sandboxed remote process exits, the executor:
1. waits the same short output grace period used by local unified exec;
2. reads the output currently available in the existing retained output
buffer;
3. runs the existing heuristic using the exit code and common denial
messages;
4. stores the yes/no result before publishing the process exit.
This deliberately matches the old local unified-exec behavior. It does
not add a new streaming classifier, another output buffer, or stronger
output-retention guarantees.
### The protocol reports a portable boolean
`process/read` gains `sandboxDenied`:
```json
{
"exited": true,
"exitCode": 1,
"closed": false,
"sandboxDenied": true
}
```
The field defaults to `false` when an older executor omits it. The
response does not expose the executor sandbox implementation or
executor-native paths.
### Unified exec uses the existing error path
The exec-server client carries `sandboxDenied` into the unified process
state. If it is true, unified exec returns the existing `SandboxDenied`
error instead of trying to classify remote output using an
orchestrator-side sandbox type.
Remote process exit remains visible as soon as the process exits. This
PR does not wait for stdout or stderr to close and does not change the
existing process lifecycle.
## Scope
This PR is intentionally limited to matching the existing local
unified-exec behavior for the initial command execution path.
It does not add:
- incremental denial tracking across the full output stream;
- new denial handling for commands completed later through
`write_stdin`;
- new guarantees for preserving the semantic flag during the narrow
reconnect-recovery race.
Those can be considered separately if the same behavior is added for
local execution.
## Test coverage
One remote end-to-end integration test covers the complete intended
flow:
```text
remote read-only sandbox
-> denied write
-> executor reports the denial
-> Codex requests approval
-> user approves
-> retry succeeds on the remote executor
```
Existing lifecycle coverage continues to verify that remote process exit
is reported before late output streams close.
## Why
Nonblocking environment snapshots allow a turn to reach the model while
a remote environment is still starting. The initial context can describe
that environment as still loading, but nothing currently refreshes the
model-visible environment context when startup finishes during the same
turn.
This adds the first request-scoped reconciliation slice on top of
#28683. It is gated by `DeferredExecutor` and intentionally updates only
model-visible environment context; tools and other environment-derived
state will migrate separately.
## What
- Add a minimal `StepContext` containing the environment snapshot
captured before each sampling request.
- Render attached environments with their resolved shell and starting
environments with `still loading`.
- Track the latest environment state recorded in model history and
append a bounded update only when it changes.
- Seed that baseline from full initial context so ready-at-start
environments are not duplicated.
- Clear the in-memory baseline when history is rewritten so replacement
history can be refreshed safely.
## Testing
- `just test -p codex-core deferred_executor`
- `just test -p codex-core
environment_context_baseline_deduplicates_until_history_is_replaced`
The integration coverage verifies that a pending environment reaches the
first request, the ready state reaches the next request, later requests
do not duplicate it, and ready-at-start environments remain
single-injected.
<details>
<summary>Live verification</summary>
- Connected to a real remote executor with startup deliberately delayed
and forced three sampling requests in one turn.
- Inspected the raw model inputs: request 1 showed the remote
environment as `still loading`, request 2 appended its ready shell and
cwd, and request 3 contained no duplicate ready update.
- With the feature disabled, startup waited for the delayed executor and
the first request contained only the ready environment.
- With a synchronously ready environment and the feature enabled, the
first request contained one environment context with no duplicate.
- Executed `pwd` and read a marker file through the remote process
runner; the command exited successfully and returned the remote cwd and
marker contents.
</details>
## Why
Remote environments may still be resolving when Codex creates a session
or turn. Waiting for the existing all-or-nothing environment snapshot
can hold startup until the selected environment is usable.
Behind the default-off `deferred_executor` feature, let callers take a
useful snapshot immediately: completed environments remain available
normally, while unfinished environments are reported without blocking
startup. With the feature disabled, snapshots preserve the existing
blocking behavior.
Depends on #28674.
## What changed
- Store one ordered list of selected environments in
`ThreadEnvironments`. Each selection owns one shared resolution that
produces its complete `TurnEnvironment`.
- Start new resolutions in the background with `remote_handle()`,
allowing snapshots and the future wait tool to share the same result
while cancellation follows the retained handles.
- Make `snapshot()` a read-only operation: nonblocking snapshots collect
completed resolutions and retain handles for unfinished ones, while
blocking snapshots await every resolution.
- Replace completed failed resolutions from the current manager entry
and log when failed environments are omitted.
- Return attached and starting environments as a point-in-time view, and
count starting environments when deciding whether a snapshot is
local-only.
- Keep existing consumers attached-only. `to_selections()` derives from
attached environments, so child threads do not inherit an environment
that is still starting.
## Test plan
- `just test -p codex-core environment_selection`
- `just test -p codex-core
deferred_executor_reaches_model_before_remote_environment_is_ready`
## Landing note
Keep `deferred_executor` disabled for slow-starting executors until
configurable `environment/add` connection timeouts and caller support
land. When enabled, an environment that attaches after session startup
may remain absent from environment-derived model context, tools,
instructions, skills, and related state until follow-up refresh work
lands.
## Why
We need to avoid resolving shells on the app-server's host for remote
environments. We might make it possible to do fancier shell resolution
from remote envs but for now just require the model to produce a shell
that matches the environment's default.
This gets my e2e demo working for shell commands after #28854 moved
shell resolution to PathUri and caused remote envs to hit the fallback
shell when the shell wasn't available on the host.
## What
Remote `exec_command` calls now accept only the environment's reported
default shell name or exact path, and execute with that reported path.
Other explicit shells return a concise error. A Wine-backed integration
test covers explicit PowerShell execution in the Windows cwd.