35 Commits

  • core: make AGENTS.md react to environment changes (#29810)
    ## 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.
  • [2/3] core: persist world state in rollouts (#29835)
    ## 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`
  • test: add app-server auto environment helper (#29746)
    ## 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.
  • core: add wait_for_environment for starting environments (#29745)
    ## 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
  • core tests: rename automatic environment builder (#29728)
    ## 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.
  • test: branch on target OS instead of runner flavor (#29712)
    ## 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.
  • core: use current step environments for tools (#29547)
    ## 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`
  • chore(core) rm AskForApproval::OnFailure (#28418)
    ## Summary
    Deletes the OnFailure variant of the `AskForApproval` enum. This option
    has been deprecated since #11631.
    
    ## Testing
    - [x] Tests pass
  • core: use turn-owned world state for inline compaction (#29527)
    ## 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`
  • path-uri: clarify host-native path conversion (#29501)
    ## 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.
  • Report remote sandbox denials semantically (#29424)
    ## 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.
  • core: refresh environment context before sampling (#29073)
    ## 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>
  • [2/3] core: track starting environments in snapshots (#28683)
    ## 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.
  • core: keep remote exec on reported shell (#28983)
    ## 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.
  • [codex] Use expect in integration tests (#28441)
    The workspace denies `clippy::expect_used` in production. Although
    `clippy.toml` allows `expect` in tests, Bazel Clippy compiles
    integration-test helper code in a way that does not receive that
    exemption, which encouraged verbose `unwrap_or_else(... panic!(...))`
    and equivalent `match`/`let else` forms.
    
    This allows `clippy::expect_used` once at each integration-test crate
    root (including aggregated suites and test-support libraries), then
    replaces manual panic-based Result and Option unwraps with
    `expect`/`expect_err`. Standalone `tests/*.rs` files remain their own
    crate roots. Intentional assertion and unexpected-variant panics remain
    unchanged, and the production `expect_used = "deny"` lint remains in
    place.
    
    The cleanup is mechanical and net-negative in line count.
  • Run core integration tests against a Wine-backed Windows executor (#28401)
    ## Why
    
    We want to exercise a linux app-server against a windows exec-server
    without having to repeat every test case. This approach has slight
    precedent in the remote docker test setup.
    
    ## What
    
    Run the shared `codex-core` integration suite against Windows
    exec-server behavior from Linux. This makes cross-OS path and shell
    regressions visible while keeping unsupported cases owned by individual
    tests.
    
    - Add `local`, `docker`, and `wine-exec` test environment selection with
    legacy Docker compatibility.
    - Extend `codex_rust_crate` to generate a sharded Wine-exec variant
    using a cross-built Windows server and pinned Bazel Wine/PowerShell
    runtimes.
    - Teach remote-aware helpers about Windows paths and track temporary
    incompatibilities with source-local `skip_if_wine_exec!` calls and
    follow-up reasons.
  • [codex] exec-server honors remote environment cwd and shell (#28122)
    ## Why
    
    Next slice needed to make progress on the `remote_env_windows` test is
    to support passing a Windows cwd for the remote environment and using
    that environment's native shell. This lets the test run a real Windows
    process instead of only recording an early path or shell mismatch.
    
    ## What
    
    - change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
    `PathUri`
    - convert local cwd values to URIs when constructing selections
    - preserve a remote primary cwd instead of replacing it with the local
    legacy fallback
    - prefer the selected environment's discovered shell for unified exec,
    falling back to the session shell when unavailable
    - convert back to a host-native absolute path at current native-only
    consumer boundaries
    - reject or deny unsupported foreign cwd values at the existing
    request-permissions boundary, with TODOs for its future migration
    - extend the hermetic Wine test to execute Windows PowerShell in
    `C:\windows` and verify successful process completion
    - record the current app-server rejection against the same Wine-backed
    remote Windows fixture when its cwd is supplied as a native Windows path
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • [codex] resolve environment shell metadata eagerly (#27709)
    ## Why
    
    Turn construction passed resolved environments through several layers
    while leaving the environment shell unresolved. As a result,
    model-visible environment context could fall back to the session shell
    instead of reporting the selected remote environment's shell.
    
    Resolve environment metadata at the turn-context boundary so each turn
    carries the shell that belongs to its selected environment. Keep request
    validation in app-server, where invalid selections can be returned as
    straightforward JSON-RPC errors without coupling core turn construction
    to that policy.
    
    ## What changed
    
    - resolve environment selections eagerly in
    `new_turn_context_from_configuration`
    - store the full resolved `Shell` on each `TurnEnvironment`
    - simplify the now-redundant resolved-environment constructor plumbing
    - keep duplicate and unknown-environment validation as a small
    app-server preflight
    - add a remote-environment integration test that runs a full
    `test_codex` turn and verifies the model-visible environment message
    reports `bash`
    
    ## Testing
    
    - `cargo check -p codex-core --test all -p codex-app-server`
    - `remote_test_env_exposes_bash_shell_to_model` on the Linux
    remote-executor harness
  • [codex] migrate ExecutorFileSystem paths to PathUri (#27424)
    ## Why
    
    We're moving exec-server to use PathUri for its internal path
    representations.
    
    ## What
    
    Move `ExecutorFileSystem` APIs to use `PathUri` instead of
    `AbsolutePathBuf`. Future changes will convert higher-level parts of
    exec-server.
  • Pair thread environment settings (#26687)
    ## Why
    
    Thread cwd and environment selections are a single logical setting in
    core: updating one without the other can silently desynchronize the
    next-turn execution context. This change makes that relationship
    explicit in the internal thread settings flow while preserving the
    existing app-server public API shape.
    
    ## What changed
    
    - Moved the cwd/environment pair through internal
    `ThreadSettingsOverrides.environment_settings` instead of a top-level
    internal `cwd` field.
    - Kept `thread/settings/update` public params unchanged, with app-server
    translating top-level `cwd` into the paired internal settings shape.
    - Moved `Op::UserInput` environment overrides into thread settings so
    user turns and settings updates use the same core path.
    - Updated core, app-server, MCP, memories, sample, and test callsites to
    construct the paired settings shape.
    
    ## Verification
    
    - `git diff --check`
    - Local test run starting after PR creation.
  • Require absolute cwd in thread settings (#26532)
    ## Why
    
    Thread settings cwd overrides are expected to be resolved before they
    enter core. Keeping this boundary as a plain `PathBuf` made it easy for
    core/session code to keep fallback normalization and relative-path
    resolution logic in places that should only receive an already-resolved
    cwd.
    
    This is intentionally the absolute-cwd-only slice: it does not change
    environment selection stickiness or cwd-to-default-environment fallback
    behavior.
    
    ## What changed
    
    - Changes `ThreadSettingsOverrides.cwd`,
    `CodexThreadSettingsOverrides.cwd`, and `SessionSettingsUpdate.cwd` to
    use `AbsolutePathBuf`.
    - Removes core-side cwd normalization/resolution from session settings
    updates.
    - Updates affected core/app-server test helpers and callsites to pass
    existing absolute cwd values or use `abs()` helpers.
    
    ## Validation
    
    Opening as draft so CI can start while local validation continues.
  • Add remote request permissions integration coverage (#25867)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. This PR (#25867) - Add remote request permissions integration
    coverage: verifies the selected remote environment across request,
    approval, grant reuse, and exec.
    
    This PR is stacked on #25862 and should be reviewed after #25850,
    #25858, and #25862.
    
    ## Why
    
    The environment-scoped permission stack needs one end-to-end check that
    exercises the CCA-shaped path, not only unit-level parsing. This
    verifies that a model-sent `environmentId` on `request_permissions`
    reaches the approval event, stores the grant under the selected
    environment, and is reused by a later tool call in that same
    environment.
    
    ## What Changed
    
    - Adds a remote executor integration test for `request_permissions` with
    `environmentId: remote` and a relative write root.
    - Asserts the permission event reports the remote environment and cwd,
    and that the normalized grant resolves under the remote cwd.
    - Approves the grant, then runs a remote `exec_command` without explicit
    per-call permissions and verifies it completes without another exec
    approval and writes only in the remote filesystem.
    
    ## Verification
    
    - Not run locally per instruction.
    - `git diff --check`
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • [3 of 7] Remove UserTurn (#23075)
    **Stack position:** [3 of 7]
    
    ## Summary
    
    This PR finishes the input-op consolidation by moving the remaining
    `Op::UserTurn` callers onto `Op::UserInput` and deleting `Op::UserTurn`.
    This touches a lot of files, but it is a low-risk mechanical migration.
    
    ## Stack
    
    1. [1 of 7] [Add thread settings to
    UserInput](https://github.com/openai/codex/pull/23080)
    2. [2 of 7] [Remove
    UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
    3. [3 of 7] [Remove
    UserTurn](https://github.com/openai/codex/pull/23075) (this PR)
    4. [4 of 7] [Placeholder for OverrideTurnContext
    cleanup](https://github.com/openai/codex/pull/23087)
    5. [5 of 7] [Replace OverrideTurnContext with
    ThreadSettings](https://github.com/openai/codex/pull/22508)
    6. [6 of 7] [Add app-server thread settings
    API](https://github.com/openai/codex/pull/22509)
    7. [7 of 7] [Sync TUI thread
    settings](https://github.com/openai/codex/pull/22510)
  • chore(features) rm Feature::ApplyPatchFreeform (#22711)
    ## Summary
    Removes the feature since this is effectively on by default in all cases
    where we should use it, or can be configured via models.json.
    
    ## Testing
    - [x] unit tests pass
  • Fix remote environment test fixtures (#22572)
    ## Why
    The Docker remote-env coverage was failing before it reached the
    behavior those tests are meant to exercise. The remote-aware test
    fixture only registered the remote environment, so tests that
    intentionally select both `local` and `remote` could not start a turn.
    After that was fixed, two tests exposed stale fixtures: the approval
    test was auto-approving under workspace-write, and the remote
    `view_image` test was writing invalid PNG bytes.
    
    ## What Changed
    - Added `EnvironmentManager::create_for_tests_with_local(...)` so tests
    can keep the provider default while also selecting `local` explicitly.
    - Updated `build_remote_aware()` to use that test-only manager when a
    remote exec-server URL is present.
    - Changed the remote apply-patch approval helper to use
    `SandboxPolicy::new_read_only_policy()` so the test actually exercises
    approval caching per environment.
    - Replaced the hardcoded remote `view_image` PNG blob with the existing
    `png_bytes(...)` helper so the test uses a valid image fixture.
    
    ## Validation
    Ran these isolated Docker remote-env tests on the devbox with
    `$remote-tests` setup:
    -
    `suite::remote_env::apply_patch_freeform_routes_to_selected_remote_environment`
    -
    `suite::remote_env::apply_patch_approvals_are_remembered_per_environment`
    -
    `suite::remote_env::apply_patch_intercepted_exec_command_routes_to_selected_remote_environment`
    -
    `suite::remote_env::exec_command_routes_to_selected_remote_environment`
    - `suite::view_image::view_image_routes_to_selected_remote_environment`
    
    All five pass.
  • Support multi-environment apply_patch selection (#21617)
    ## Summary
    - add multi-environment apply_patch routing for both freeform and
    function-call tool flows
    - parse and reconcile the optional environment selector in the main
    apply_patch parser, then verify against the selected environment in the
    handler
    - carry environment_id through runtime and approval surfaces so
    remote-targeted patches stay explicit end to end
    
    ## Testing
    - just fmt
    - remote exec-server e2e: `cargo test -p codex-core --test all
    apply_patch_multi_environment_uses_remote_executor -- --nocapture` on
    dev via `scripts/test-remote-env.sh`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Route process tools to selected environments (#20647)
    ## Why
    When a turn exposes multiple selected environments, shell-style tools
    need a model-facing way to identify the intended target environment and
    handlers need to resolve that target before parsing cwd-relative
    permission fields or launching processes.
    
    This PR scopes that rollout to process tools. Filesystem-oriented tools
    such as `apply_patch`, `view_image`, and `list_dir` are intentionally
    left for follow-up slices.
    
    ## What Changed
    - Adds an `include_environment_id` option to shell-style tool schema
    builders.
    - Exposes optional `environment_id` on `shell`, `shell_command`, and
    `exec_command` only when `ToolEnvironmentMode::Multiple` is active.
    - Adds a shared handler helper that parses `environment_id` and
    `workdir` from JSON function-call arguments and returns the selected
    `Environment` plus effective absolute cwd.
    - Uses that helper in `shell`, `shell_command`, and `exec_command`
    handling so process execution uses the selected environment filesystem
    and cwd.
    - Changes `ExecCommandRequest` to carry a required resolved `cwd`,
    removing the process-manager fallback to the primary turn cwd for new
    exec commands.
    - Leaves `write_stdin` unchanged because it targets an existing process
    id, not a new environment.
    
    ## Testing
    - Added unit coverage for process-tool schema exposure, selected
    environment resolution, primary fallback, no-environment handling,
    unknown environment ids, and resolving cwd-relative permission paths
    against the selected environment cwd.
    - Added a remote-suite e2e coverage case for `exec_command` routing
    across explicit zero environments, one local environment, and
    local+remote environments.
    - Ran `just fmt` and `git diff --check`.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • permissions: remove legacy read-only access modes (#19449)
    ## Why
    
    `ReadOnlyAccess` was a transitional legacy shape on `SandboxPolicy`:
    `FullAccess` meant the historical read-only/workspace-write modes could
    read the full filesystem, while `Restricted` tried to carry partial
    readable roots. The partial-read model now belongs in
    `FileSystemSandboxPolicy` and `PermissionProfile`, so keeping it on
    `SandboxPolicy` makes every legacy projection reintroduce lossy
    read-root bookkeeping and creates unnecessary noise in the rest of the
    permissions migration.
    
    This PR makes the legacy policy model narrower and explicit:
    `SandboxPolicy::ReadOnly` and `SandboxPolicy::WorkspaceWrite` represent
    the old full-read sandbox modes only. Split readable roots, deny-read
    globs, and platform-default/minimal read behavior stay in the runtime
    permissions model.
    
    ## What changed
    
    - Removes `ReadOnlyAccess` from
    `codex_protocol::protocol::SandboxPolicy`, including the generated
    `access` and `readOnlyAccess` API fields.
    - Updates legacy policy/profile conversions so restricted filesystem
    reads are represented only by `FileSystemSandboxPolicy` /
    `PermissionProfile` entries.
    - Keeps app-server v2 compatible with legacy `fullAccess` read-access
    payloads by accepting and ignoring that no-op shape, while rejecting
    legacy `restricted` read-access payloads instead of silently widening
    them to full-read legacy policies.
    - Carries Windows sandbox platform-default read behavior with an
    explicit override flag instead of depending on
    `ReadOnlyAccess::Restricted`.
    - Refreshes generated app-server schema/types and updates tests/docs for
    the simplified legacy policy shape.
    
    ## Verification
    
    - `cargo check -p codex-app-server-protocol --tests`
    - `cargo check -p codex-windows-sandbox --tests`
    - `cargo test -p codex-app-server-protocol sandbox_policy_`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19449).
    * #19395
    * #19394
    * #19393
    * #19392
    * #19391
    * __->__ #19449
  • exec-server: expose arg0 alias root to fs sandbox (#19016)
    ## Why
    
    The post-merge `rust-ci-full` run for #18999 still failed the Ubuntu
    remote `suite::remote_env` sandboxed filesystem tests. That run checked
    out merge commit `ddde50c611e4800cb805f243ed3c50bbafe7d011`, so the arg0
    guard lifetime fix was present.
    
    The Docker-backed failure had two remaining pieces:
    
    - The sandboxed filesystem helper needs to execute Codex through the
    `codex-linux-sandbox` arg0 alias path. The helper sandbox was only
    granting read access to the real Codex executable parent, so the alias
    parent also has to be visible inside the helper sandbox.
    - The remote-env tests were building sandbox contexts with
    `FileSystemSandboxContext::new()`, which captures the local test runner
    cwd. In the Docker remote exec-server, that host checkout path does not
    exist, so spawning the filesystem helper failed with `No such file or
    directory` before the helper could process the request.
    
    ## What Changed
    
    - Track all helper runtime read roots instead of a single root.
    - Add both the real Codex executable parent and the
    `codex-linux-sandbox` alias parent to sandbox readable roots.
    - Avoid sending an unused local cwd in remote filesystem sandbox
    contexts when the permission profile has no cwd-dependent entries.
    - Build the Docker remote-env test sandbox contexts with a cwd path that
    exists inside the container.
    - Add unit coverage for the alias-parent root and remote sandbox cwd
    handling.
    
    ## Verification
    
    - `cargo test -p codex-exec-server`
    - `cargo test -p codex-core
    remote_test_env_sandboxed_read_allows_readable_root`
    - `just fix -p codex-exec-server`
    - `just fix -p codex-core`
  • Stabilize exec-server filesystem tests in CI (#17671)
    ## Summary\n- add an exec-server package-local test helper binary that
    can run exec-server and fs-helper flows\n- route exec-server filesystem
    tests through that helper instead of cross-crate codex helper
    binaries\n- stop relying on Bazel-only extra binary wiring for these
    tests\n\n## Testing\n- not run (per repo guidance for codex changes)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Run exec-server fs operations through sandbox helper (#17294)
    ## Summary
    - run exec-server filesystem RPCs requiring sandboxing through a
    `codex-fs` arg0 helper over stdin/stdout
    - keep direct local filesystem execution for `DangerFullAccess` and
    external sandbox policies
    - remove the standalone exec-server binary path in favor of top-level
    arg0 dispatch/runtime paths
    - add sandbox escape regression coverage for local and remote filesystem
    paths
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    - remote devbox: `cd codex-rs && bazel test --bes_backend=
    --bes_results_url= //codex-rs/exec-server:all` (6/6 passed)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Make AGENTS.md discovery FS-aware (#15826)
    ## Summary
    - make AGENTS.md discovery and loading fully FS-aware and remove the
    non-FS discover helper
    - migrate remote-aware codex-core tests to use TestEnv workspace setup
    instead of syncing a local workspace copy
    - add AGENTS.md corner-case coverage, including directory fallbacks and
    remote-aware integration coverage
    
    ## Testing
    - cargo test -p codex-core project_doc -- --nocapture
    - cargo test -p codex-core hierarchical_agents -- --nocapture
    - cargo test -p codex-core agents_md -- --nocapture
    - cargo test -p codex-tui status -- --nocapture
    - cargo test -p codex-tui-app-server status -- --nocapture
    - just fix
    - just fmt
    - just bazel-lock-update
    - just bazel-lock-check
    - just argument-comment-lint
    - remote Linux executor tests in progress via scripts/test-remote-env.sh
  • Add remote env CI matrix and integration test (#14869)
    `CODEX_TEST_REMOTE_ENV` will make `test_codex` start the executor
    "remotely" (inside a docker container) turning any integration test into
    remote test.