## Why
Guardian review turns already submit a read-only `PermissionProfile`,
which is the permissions model the runtime should honor. Passing the
equivalent legacy `SandboxPolicy` through `ThreadSettingsOverrides`
keeps two representations of the same read-only constraint alive on this
path and makes the guardian flow depend on compatibility plumbing that
is being phased out.
## What Changed
- Set `sandbox_policy` to `None` when the guardian review session
submits its child `Op::UserInput`.
- Keep `permission_profile: Some(PermissionProfile::read_only())` and
`approval_policy: Some(AskForApproval::Never)`, so the guardian review
remains read-only and cannot request approvals.
- Remove the now-unused `SandboxPolicy` import and redundant comment
from `codex-rs/core/src/guardian/review_session.rs`.
## Verification
Not run locally; this is a narrow cleanup of redundant thread-settings
override state.
## Summary
- update the app-server image generation integration test to use
`TestAppServer`
- completes the test helper rename from #25701 for this newer test file
## Validation
- `cargo fmt -- --config imports_granularity=Item`
- `cargo check -p codex-app-server --test all`
Note: `just fmt` ran Rust formatting but failed on Python/SDK formatting
because the sandbox could not access the local `uv` cache.
## Summary
- Adapts the moved `codex-cloud-config` crate from the legacy cloud
requirements endpoint to the new config bundle endpoint.
- Switches runtime consumers from `CloudRequirementsLoader` to
`CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
config and requirements.
- Removes the legacy cloud requirements domain loader path.
## Details
This intentionally keeps `codex-cloud-config` monolithic for review
lineage: the previous PR establishes the crate move, and this PR shows
the behavior change against that moved implementation. A follow-up PR
splits the module back into focused files.
The new bundle path preserves the important cloud requirements loader
semantics where intended: account-scoped signed cache, 30 minute TTL, 5
minute refresh cadence, retry/backoff, auth recovery, and fail-closed
startup loading. The cached payload changes from a single requirements
TOML string to the backend-delivered bundle, and validation rejects
malformed config or requirements fragments before cache write/use.
## Summary
- carry `workspace_kind` from Responses API client metadata into the
turn resolved analytics fact
- serialize the optional value on `codex_turn_event`
- cover both the turn metadata source and turn event serialization
The `workspace_kind` tells us whether a thread had a project attached vs
projectless. this is an indicator for who is adopting Codex for
knowledge work outside of coding
## Testing
- `env UV_CACHE_DIR=/private/tmp/uv-cache
/private/tmp/cargo-tools/bin/just fmt`
- `env PATH=/private/tmp/cargo-tools/bin:$PATH
CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
/private/tmp/cargo-tools/bin/just test -p codex-analytics`
- `env PATH=/private/tmp/cargo-tools/bin:$PATH
CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
/private/tmp/cargo-tools/bin/just test -p codex-core turn_metadata`
Paired with openai/openai#970661, which keeps forwarding the same
metadata key through Responses API headers.
## Why
Fixes#24944.
On Windows, app-server resume could reject an active running thread when
the requested session path used normal `C:\...` form and the
already-running path used verbatim `\\?\C:\...` form. The paths point at
the same JSONL file, but the resume stale-path guard compared raw
`PathBuf`s, so desktop resume and heartbeat flows could fail with a
mismatched-path error.
## What Changed
- Compare requested and active rollout paths with
`path_utils::paths_match_after_normalization`.
- Extend the existing running-thread mismatched-path test with a
Windows-only same-file resume case before the stale-path rejection.
## Verification
- `just test -p codex-app-server
thread_resume_rejects_mismatched_path_for_running_thread_id`
## Summary
- Move Azure Trusted Signing values out of reusable workflow-call
secrets and into the `azure-artifact-signing` environment scope
- Attach the Windows signing job to the `azure-artifact-signing`
environment so it can resolve the signing secrets directly
- Stop inheriting caller secrets for the Windows release reusable
workflow
## Validation
- `git diff --check -- .github/workflows/rust-release.yml
.github/workflows/rust-release-windows.yml`
- `ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path);
puts "ok #{path}" }' .github/workflows/rust-release.yml
.github/workflows/rust-release-windows.yml`
## Summary
- pin the Python SDK runtime package to `openai-codex-cli-bin==0.136.0`
so Ubuntu/glibc installs resolve a compatible wheel
- refresh generated SDK artifacts and lock data for the runtime update
- keep newly generated client-message-id wire models internal to the
generated protocol layer
## Dependency
- merge #25906 first so the Python SDK release publishes both manylinux
and musllinux runtime wheels before publishing the package with this pin
## Validation
- ran `just fmt`
- regenerated the Python public API helpers
- validated the edited workflow YAML
- CI passed 29/29 checks
## 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. This PR (#25862) - Propagate permission approval environment id:
carries the selected environment id through approval events, app-server
requests, TUI prompts, and delegate forwarding.
4. #25867 - Add remote request permissions integration coverage:
verifies the selected remote environment across request, approval, grant
reuse, and exec.
This PR is stacked on #25858, and #25867 is stacked on this PR.
## Why
PR2 lets the model bind a `request_permissions` call to a selected
environment, but the approval event and client-facing request still
needed to carry that binding. For CCA, the user-facing prompt and
delegated approval path should know which environment the grant applies
to instead of relying on cwd alone.
## What Changed
- Added optional `environmentId` to `RequestPermissionsEvent`.
- Emit the selected environment id from core permission approval events.
- Preserve the environment id through delegate forwarding, including
cwd-based delegated requests.
- Added `environmentId` to app-server permission approval params,
generated schema/TypeScript artifacts, and README examples.
- Preserve and display the environment id in TUI permission approval
prompts.
- Updated focused core, app-server protocol, and TUI conversion
coverage.
## Testing
Not run locally per instruction. Performed read-only `git diff --check`.
## Summary
- Teach the Windows release prebuild staging step to locate Rust/MSVC
PDBs emitted with crate-style underscore names.
- Stage PDBs under the shipped hyphenated binary names so the downstream
symbol archive step keeps the same artifact contract.
- Keep a fallback for already-hyphenated PDB names and fail with a clear
diagnostic if neither form exists.
## Root cause
The recent symbol publishing change in #25649 started copying
`${binary}.pdb` from `target/<triple>/release` during Windows prebuild
staging. Cargo still emits the `.exe` with the hyphenated binary name,
but MSVC PDBs for hyphenated Rust crates are emitted with underscores,
for example `codex_app_server.pdb` for `codex-app-server.exe`. The
release workflow was still building into the expected directory; the new
PDB copy step was looking for the wrong filename.
## Impact
This unblocks the `rust-release` Windows prebuilt-binary jobs for
hyphenated binaries while preserving the hyphenated PDB names consumed
by the final Windows release packaging and symbol archive steps.
## Validation
- `just fmt` from `codex-rs`
- `git diff --check -- .github/workflows/rust-release-windows.yml`
- Parsed `.github/workflows/rust-release-windows.yml` as YAML locally
- Local bash staging sanity test for both underscore-emitted and
hyphenated PDB filenames
## Why
Standalone image-generation extensions emitted turn items through the
low-level event path, bypassing host-owned finalization such as image
persistence and contributor processing. At the same time, the
generated-image save-path hint must remain visible to the model through
the extension tool's `FunctionCallOutput`, rather than the legacy
built-in developer-message path.
## What changed
- Extended `ExtensionTurnItem` to support image-generation items while
keeping the extension-facing emitter API limited to `emit_started` and
`emit_completed`.
- Routed extension completion through core `finalize_turn_item`, so
standalone image-generation items receive host-owned processing and
persisted `saved_path` values before publication.
- Kept legacy built-in image generation on its existing
developer-message hint path, while standalone image generation returns
its deterministic saved-path hint in `FunctionCallOutput`.
- Shared the image artifact path and output-hint formatting used by core
and the image-generation extension.
- Passed thread identity through extension tool calls so standalone
image generation can construct the same intended artifact path as core.
- Added an app-server integration test covering real standalone image
generation, saved artifact publication, model-visible output hint
wiring, and absence of the legacy developer-message hint.
## Validation
- `just fmt`
- `just test -p codex-image-generation-extension`
- `just test -p codex-web-search-extension`
- `just test -p codex-goal-extension`
- `just test -p codex-memories-extension`
- Targeted `codex-core` tests for image save history, extension
completion finalization, and contributor execution
- `just test -p codex-app-server
standalone_image_generation_returns_saved_path_hint_to_model`
- `just fix -p codex-core`
- `just fix -p codex-image-generation-extension`
- `just bazel-lock-update`
- `just bazel-lock-check`
## Stack
1. #25850 - Key request-permission grants by environment: stores and
applies sticky permission grants per environment id.
2. This PR (#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. #25867 - Add remote request permissions integration coverage:
verifies the selected remote environment across request, approval, grant
reuse, and exec.
This PR is stacked on #25850; #25862 and #25867 are stacked on this PR.
## Why
PR1 made request-permission grants internally environment-keyed, but the
model-facing `request_permissions` tool could still only target the
primary environment. For CCA and multi-environment turns, the tool needs
an explicit way to bind a permission request to a selected attached
environment before resolving relative paths.
## What Changed
- Added optional `environmentId` to `RequestPermissionsArgs`, with
`environment_id` accepted as an alias.
- Exposed `environmentId` in the `request_permissions` tool schema and
description.
- Resolve the selected environment before parsing filesystem permission
paths, so relative paths bind to the selected environment cwd.
- Route validated tool calls through
`request_permissions_for_environment` directly instead of duplicating
environment lookup in `Session::request_permissions`.
- Reject unknown environment ids with a model-facing error.
- Updated focused request-permissions and Guardian call sites for the
new optional field.
## Testing
Not run locally per instruction.
## Summary
- add analytics-only `CodexErr` telemetry to `codex_turn_event` while
leaving existing `turn_error` unchanged
- record terminal `CodexErr` facts from core immediately before the
existing turn error event is sent
- emit source-truth `codex_error_*` fields for downstream analytics,
including the raw `CodexErr::InvalidRequest(String)` message as
`codex_error_subreason`
## Validation
- `just test -p codex-analytics`
- attempted `just test -p codex-core`, but the local run timed out
across unrelated integration suites in this environment and is not being
used as validation
## Stack
1. This PR (#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. #25867 - Add remote request permissions integration coverage:
verifies the selected remote environment across request, approval, grant
reuse, and exec.
#25858, #25862, and #25867 are stacked on this PR and should be reviewed
after it.
## Why
Multi-environment CCA turns can attach both local and remote executors,
but request-permission grants were still effectively cwd-only. Pending
permission requests tracked a cwd, while stored turn/session grants had
no environment identity, so sticky grants could be reused through the
wrong executor context.
This makes the first permission-grant step environment-aware without
changing the external `request_permissions` payload shape: omitted
environment targeting remains bound to the primary turn environment.
## What Changed
- Store turn- and session-scoped request-permission grants by
`environment_id`.
- Keep the selected `TurnEnvironmentSelection` with pending
`request_permissions` calls so approval responses normalize and record
grants against the same environment.
- Resolve relative `request_permissions` file paths against the primary
turn environment cwd instead of deprecated `turn.cwd`.
- Apply sticky grants in `shell`, `exec_command`, and `apply_patch` by
selected environment id while still using the actual tool cwd for
cwd-relative permission materialization.
- Update Guardian and request-permissions coverage for the
environment-keyed grant behavior.
## Testing
Not run locally. Added or updated focused coverage for:
- `request_permission_grants_are_environment_keyed`
-
`request_permissions_tool_resolves_relative_paths_against_primary_environment`
- related Guardian/request-permissions sticky grant tests
Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution.
The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary.
Keep the Windows exec tests aligned with that shape by constructing PermissionProfile values through the highest-level available helpers instead of routing test setup through SandboxPolicy compatibility conversions.
Validation:
- just test -p codex-sandboxing
- just test -p codex-core windows_restricted_token
- CI for the follow-up exec_tests cleanup
## Why
PR #25905 intentionally adds a failing `codex-core` unit test, but its
[Bazel test on Windows
check](https://github.com/openai/codex/actions/runs/26837526950/job/79135369259)
passed. That shows the Bazel configuration introduced by #25156 is not
behaving as expected, so revert it while the configuration can be
investigated separately.
## What changed
Revert #25156 in full, restoring the previous Bazel remote
configuration, CI scripts, workflows, `rusty_v8` handling, and
documentation. This removes the shared BuildBuddy wrapper and its tests.
## Validation
Not run locally; this exact revert was prioritized for a fast rollback.
Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution.
The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary.
Keep the Windows exec tests aligned with that shape by constructing PermissionProfile values through the highest-level available helpers instead of routing test setup through SandboxPolicy compatibility conversions.
Validation:
- just test -p codex-sandboxing
- just test -p codex-core windows_restricted_token
- CI for the follow-up exec_tests cleanup
## Why
Permission profiles that extend a built-in profile should behave like
other TOML inheritance: parent entries provide defaults, and child keys
override matching fields before the profile is compiled.
That was not true for `:workspace`. Previously, a profile with `extends
= ":workspace"` seeded the compiled runtime
`PermissionProfile::workspace_write()` policy and then appended child
filesystem entries. A child override such as `":tmpdir" = "read"`
therefore left the inherited `":tmpdir" = "write"` entry in the final
policy. Since same-target `write` wins over `read` during runtime
resolution, the child override was ineffective.
This also needs a clear source of truth for the built-in profiles. The
protocol-level sandbox policy constructors now define the raw built-in
filesystem entries, and both `PermissionProfile` presets and
config-profile inheritance derive from those same values.
## What Changed
- Add a canonical `FileSystemSandboxPolicy::read_only()` constructor
while keeping the read-only and workspace-write raw filesystem entries
explicit and independent.
- Derive `PermissionProfile::read_only()` from
`FileSystemSandboxPolicy::read_only()`;
`PermissionProfile::workspace_write()` continues to derive from
`FileSystemSandboxPolicy::workspace_write()`.
- Build extensible `:read-only` and `:workspace` parent profiles by
projecting those canonical sandbox policies into
`PermissionProfileToml`, then merge user overrides at the TOML layer
before compilation.
- Add config parsing support for `:slash_tmp` so the built-in
`:workspace` parent can be expressed in the same TOML-shaped filesystem
table as user profiles.
- Document that `PermissionsToml::resolve_profile()` returns an
already-merged `PermissionProfileToml`, and return that profile directly
after removing the resolved-profile wrapper.
- Extend the config test for `extends = ":workspace"` to assert that
inherited `":slash_tmp" = "write"` is preserved and that a child
`":tmpdir" = "read"` entry replaces the inherited `write` entry.
## Verification
- `just test -p codex-config`
- `just test -p codex-protocol`
- `just test -p codex-core
permissions_profiles_resolve_extends_parent_first_with_child_overrides`
- `just test -p codex-core
default_permissions_profile_can_extend_builtin_workspace`
- `just test -p codex-core`
- Result: 2596 passed, 4 failed, 1 timed out.
- The failures were existing sandbox/environment-sensitive tests
unrelated to this permissions change:
`suite::user_shell_cmd::user_shell_command_does_not_set_network_sandbox_env_var`,
`suite::user_shell_cmd::user_shell_command_history_is_persisted_and_shared_with_model`,
`suite::abort_tasks::interrupt_persists_turn_aborted_marker_in_next_request`,
`suite::abort_tasks::interrupt_tool_records_history_entries`, and
`thread_manager::tests::start_thread_uses_all_default_environments_from_codex_home`.
Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution.
The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary.
Keep the Windows exec tests aligned with that shape by constructing PermissionProfile values directly, without routing test setup through SandboxPolicy compatibility conversions.
Validation:
- just test -p codex-sandboxing
- just test -p codex-core windows_restricted_token
- CI for the follow-up exec_tests cleanup
## Why
Bazel remote configuration was selected in several CI scripts and
workflow steps. That made the BuildBuddy tenant policy easy to duplicate
and harder to audit, especially for fork pull requests that must not use
the OpenAI tenant.
This builds on
[sluongng/buildbuddy-ci-host-routing](https://github.com/openai/codex/compare/main...sluongng:codex:sluongng/buildbuddy-ci-host-routing)
and consolidates the policy in one place.
## What to do if this breaks you
See `codex-rs/docs/bazel.md` for details. TLDR:
1. make a BuildBuddy API key and put it in `~/.bazelrc`
2. if you're an OpenAI employee, add `common
--config=buildbuddy-openai-rbe` to `user.bazelrc` in the repo root
Run `just bazel-test` to ensure it works.
Note that `just bazel-remote-test` no longer exists, you need to select
a remote configuration as documented to use RBE.
## What changed
- Add `.github/scripts/run_bazel_with_buildbuddy.py` as the shared Bazel
wrapper and Python library. It selects the OpenAI host only for trusted
upstream GitHub Actions runs, routes keyed fork runs to the generic
host, and falls back to local Bazel execution when no key is available.
- Move endpoint selection into explicit `.bazelrc` configurations and
update Bazel CI, query helpers, and `rusty_v8` staging to use the shared
policy. Loading-phase target-discovery queries remain local.
- Add wrapper and `rusty_v8` unit coverage, plus `just test-scripts` for
the `.github/scripts` Python tests.
- Document local Bazel usage, `user.bazelrc` setup, BuildBuddy
configurations, and CI behavior in `codex-rs/docs/bazel.md`.
## Validation
- `just test-scripts`
- `bash -n .github/scripts/run-bazel-ci.sh
.github/scripts/run-bazel-query-ci.sh
.github/scripts/run-argument-comment-lint-bazel.sh
scripts/list-bazel-clippy-targets.sh`
- `python3 -m py_compile .github/scripts/run_bazel_with_buildbuddy.py
.github/scripts/test_run_bazel_with_buildbuddy.py
.github/scripts/test_rusty_v8_bazel.py
.github/scripts/rusty_v8_bazel.py`
- `ruff check .github/scripts/run_bazel_with_buildbuddy.py
.github/scripts/test_run_bazel_with_buildbuddy.py
.github/scripts/test_rusty_v8_bazel.py
.github/scripts/rusty_v8_bazel.py`
Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution.
The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary.
Keep the Windows exec tests aligned with that shape by constructing PermissionProfile values directly, without routing test setup through SandboxPolicy compatibility conversions.
Validation:
- just test -p codex-sandboxing
- just test -p codex-core windows_restricted_token
- CI for the follow-up exec_tests cleanup
Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution.
The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary.
Keep the Windows exec tests aligned with that shape by constructing PermissionProfile values directly for non-legacy cases; SandboxPolicy remains only in tests that explicitly exercise legacy conversion.
Validation:
- just test -p codex-sandboxing
- just test -p codex-core windows_restricted_token
- CI for the follow-up exec_tests cleanup
Replace the exec-side legacy SandboxPolicy plumbing with PermissionProfile-based Windows sandbox override resolution.
The runtime still needs a legacy SandboxPolicy projection for a few compatibility surfaces and for Windows override baseline comparisons, but that projection is now derived from a single PermissionProfile instead of accepting separately materialized FileSystemSandboxPolicy and NetworkSandboxPolicy inputs. This avoids mismatched permission arguments while keeping the migration scoped to the existing compatibility boundary.
Validation:
- just test -p codex-sandboxing
- just test -p codex-core windows_restricted_token
Migrate the exec-side Windows sandbox override plumbing to pass PermissionProfile plus the split filesystem/network policies instead of accepting a legacy SandboxPolicy.
This keeps the remaining compatibility projection local to the writable-root comparison that still needs it, and removes ExecRequest::compatibility_sandbox_policy without touching the broader config, protocol, app-server, telemetry, or session surfaces from #25450.
Migrate the exec-side Windows sandbox override plumbing to pass PermissionProfile plus the split filesystem/network policies instead of accepting a legacy SandboxPolicy.
This keeps the remaining compatibility projection local to the writable-root comparison that still needs it, and removes ExecRequest::compatibility_sandbox_policy without touching the broader config, protocol, app-server, telemetry, or session surfaces from #25450.
Migrate the exec-side Windows sandbox override plumbing to pass PermissionProfile plus the split filesystem/network policies instead of accepting a legacy SandboxPolicy.
This keeps the remaining compatibility projection local to the writable-root comparison that still needs it, and removes ExecRequest::compatibility_sandbox_policy without touching the broader config, protocol, app-server, telemetry, or session surfaces from #25450.
## Summary
- skip startup websocket prewarm setup when the model client has
Responses-over-WebSocket disabled
- avoid making HTTP-only sessions build prewarm prompt/tool state that
cannot produce a reusable websocket session
## Why
Recent macOS timing flakes were timing out while waiting for first-turn
events in HTTP-only core tests. Startup prewarm is only useful for
websocket-capable providers, but it was scheduled for every session. For
HTTP-only test providers this added unnecessary async startup work
before the regular turn could reach the mocked response flow.
## Testing
- bazel test //codex-rs/core:core-all-test
--test_filter=suite::auto_review::remote_model_override_uses_catalog_model_for_strict_auto_review
--test_output=errors
- bazel test //codex-rs/core:core-all-test
--test_filter=suite::request_permissions_tool::approved_folder_write_request_permissions_unblocks_later_apply_patch
--test_output=errors
Migrate the exec-side Windows sandbox override plumbing to pass PermissionProfile plus the split filesystem/network policies instead of accepting a legacy SandboxPolicy.
This keeps the remaining compatibility projection local to the writable-root comparison that still needs it, and removes ExecRequest::compatibility_sandbox_policy without touching the broader config, protocol, app-server, telemetry, or session surfaces from #25450.