# Why
When an organization requires the elevated Windows sandbox, Codex
launches an elevated helper to provision users, configure firewall and
ACL rules, and lock persistent sandbox directories.
We observed that closing the helper after setup started could leave the
machine partially initialized while the TUI still announced **Sandbox
ready**. Model-only turns continued to work, but the first shell command
retried setup and failed with Windows cancellation error `1223`.
This was not an enforcement bypass; command execution continued to fail
closed. The issue was a false readiness signal: `setup_marker.json` was
written during user provisioning, before the remaining setup stages had
completed.
# What
Treat `setup_marker.json` as the commit record for Windows sandbox
setup:
1. Before full or provisioning setup begins, remove the existing marker
and create the final marker path with a protected ACL.
2. Keep the marker empty and therefore invalid while setup is in
progress. Sandbox users cannot read, modify, or replace it.
3. Run every synchronous setup stage.
4. After setup succeeds, write the valid marker contents without
changing its ACL.
5. After the helper exits successfully, verify the existing readiness
check before enabling the sandbox.
If setup is canceled or fails, the marker remains invalid and Codex
reports setup as incomplete instead of announcing readiness.
Refresh-only and read-ACL-only helper runs continue to leave the marker
untouched. The setup version remains `5` to avoid forcing all existing
Windows users through elevated setup again.
# Verification
- Added coverage confirming sandbox users cannot read or modify the
setup marker after elevated setup.
- Added coverage confirming a successful helper exit without complete
setup artifacts is rejected.
- Ran `just test -p codex-windows-sandbox`.
## Why
PR descriptions can be visible outside the context used to generate
them. In #23710, a generated description referenced an internal
document, showing that the skill needs an explicit guardrail against
exposing confidential context.
## What changed
- Updated the `codex-pr-body` guidance to prohibit confidential
references, including codenames and OpenAI-internal URLs.
## Why
When trying to fit history under compaction limit rewrite output items
instead of removing them entirely. Otherwise we're breaking
incrementality in relation to the previous response.
## Why
Model metadata can now select multi-agent v2 even when a user has not
enabled `features.multi_agent_v2` in their config. Some existing configs
still set the legacy `agents.max_threads` knob for v1 multi-agent
behavior, so treating every v2 runtime as incompatible with
`agents.max_threads` would break users whose only v2 signal came from
the model catalog.
The incompatible configuration is specifically enabling
`features.multi_agent_v2` while also setting `agents.max_threads`.
Catalog-forced v2 should use the v2 concurrency setting and ignore the
legacy v1 cap instead of rejecting the config.
## What changed
- Split config validation from runtime concurrency calculation:
`effective_agent_max_threads` now just returns the effective cap for the
resolved multi-agent runtime.
- Added explicit validation for `features.multi_agent_v2` +
`agents.max_threads` at session startup.
- Preserved catalog-selected v2 behavior when `features.multi_agent_v2`
is disabled, so existing configs with `agents.max_threads` keep
starting.
- Updated model-runtime selector coverage so a catalog v2 model still
exposes v2 tools even when `agents.max_threads` is set and the config
flag is disabled.
## Validation
- `cargo check -p codex-core --lib`
- `just test -p codex-core --lib -E
"test(multi_agent_v2_feature_rejects_agents_max_threads) |
test(catalog_v2_allows_agents_max_threads_when_feature_disabled)"`
## Why
Python SDK releases pin an exact `openai-codex-cli-bin` version, so all
eight platform runtime wheels must be available on PyPI before the SDK
package is built and published. PyPI does not support reusable workflows
as Trusted Publishers, which means OIDC-backed publishing must run from
each top-level release workflow.
## What changed
- add reusable `python-runtime-build.yml` to prepare and upload all
eight runtime wheels without publishing
- add top-level `python-runtime-release.yml` for manual runtime
publication before updating an SDK pin
- have `python-sdk-release.yml` publish and verify the prepared runtime
wheels from its own top-level trusted job before building the SDK
- verify PyPI exposes exactly the expected eight runtime wheels before
either release workflow continues
## PyPI configuration
- keep the trusted publisher for
`.github/workflows/python-sdk-release.yml` with environment `pypi`
- add a trusted publisher for
`.github/workflows/python-runtime-release.yml` with environment `pypi`
- no trusted publisher is needed for
`.github/workflows/python-runtime-build.yml`
## Validation
- parsed all three workflow YAML files
- validated all embedded shell blocks with `bash -n`
- no local tests run; relying on online CI
## Summary
Restore Windows coverage for standalone image generation in code mode.
The previous test executed a V8-backed code-mode cell on Windows CI,
where that runtime path is intentionally excluded because it is
unreliable. The test was then ignored entirely on Windows, removing
useful coverage.
This splits the test into two checks:
- All platforms verify that `image_gen__imagegen` is exposed to the
model when image generation is configured for code mode only.
- Non-Windows platforms continue to execute the full V8-backed flow and
verify that the nested image-generation call succeeds.
## Verification
- `just fmt`
- `git diff --check`
- `just test -p codex-app-server standalone_image_generation`
Result: 3 tests passed, plus the required bench smoke check.
Fixes#25950.
## Why
Forking a renamed thread could fall back to the source thread's
first-prompt title because the fork path did not preserve the source's
explicit name. That meant fork-of-renamed-fork flows could show stale
sidebar labels even though the user had renamed the parent.
## What changed
`thread/fork` now reads the source thread's distinct `name`, normalizes
it, persists it onto materialized forks, and applies it to the returned
API thread. Because the source `name` already excludes first-prompt
pseudo-titles, forks inherit only an explicit user rename instead of
stale generated metadata.
## Summary
Adds the app-server v2 `accountSession/*` protocol used by the Desktop
profile switcher and the backend account metadata client needed to
populate workspace choices.
This is the protocol layer only. The app-server lifecycle and
consolidated saved-session storage are split into a follow-up PR.
## Rust Stack
1. This PR
2. [openai/codex#25383](https://github.com/openai/codex/pull/25383) adds
app-server session lifecycle behavior and consolidated saved-session
storage.
## Validation
- Generated app-server schema fixtures are included from the existing
generation flow in the lifecycle PR where the routes are registered.
- Did not run tests per requested scope.
## Why
Local image attachments include image bytes, but the adjacent
model-visible label omits the source path. Exposing the path lets
model-selected workflows refer back to the intended local image
explicitly.
## What changed
- Include an escaped `path` attribute in model-visible local image
opening tags.
- Reuse the path-aware marker generator in rollout coverage.
- Update protocol, replay, and rollout coverage for the new request
shape.
## Validation
- `just fmt`
- `just test -p codex-protocol`
- `just test -p codex-core skips_local_image_label_text`
- `just test -p codex-core
copy_paste_local_image_persists_rollout_request_shape`
- `git diff --check`
## Summary
- Read `default_prompts` from remote plugin release metadata.
- Prefer the plural prompt list over legacy `default_prompt`.
- Fall back to `default_prompt` as a single-item list for backward
compatibility.
## Testing
- `just test -p codex-core-plugins`
- `just test -p codex-app-server`
## Summary
- pin the Python SDK runtime to `openai-codex-cli-bin==0.137.0a4`
- refresh generated protocol artifacts from `rust-v0.137.0-alpha.4`
- refresh `sdk/python/uv.lock` with all eight published runtime wheels
## Runtime publication
- published `openai-codex-cli-bin==0.137.0a4` through the
`python-sdk-release` workflow
- includes macOS, manylinux, musllinux, and Windows wheels
- publication run:
https://github.com/openai/codex/actions/runs/26905608531
## Validation
- ran `just fmt`
- generated artifacts from the `rust-v0.137.0-alpha.4` release wheel
- ran `uv lock --check --default-index https://pypi.org/simple`
- did not run tests locally, per request; CI provides the test signal
## Why
Codex-created linked worktrees do not include ignored files from the
main worktree. Bazel users who keep local overrides in `user.bazelrc`
therefore lose those settings in every new worktree.
The setup must also work on Windows and must not overwrite a file that
already exists in the worktree.
## What changed
The checked-in Codex environment now invokes
`.codex/environments/setup.py`. The script resolves the main worktree
and current worktree, then uses
`copy_from_main_worktree_to_worktree(repo_relative_path)` to copy
ignored files into new worktrees without overwriting existing
destinations.
`main()` currently copies `user.bazelrc`. Additional repository-relative
paths can be added as further calls to the same helper.
## Validation
- Ran the setup script in a linked worktree and confirmed it handles a
missing main-worktree `user.bazelrc`.
- Verified the helper copies a main-worktree file, preserves an existing
worktree file, and creates parent directories for a nested path.
## Why
#25450 attempts a broad `SandboxPolicy` removal across several unrelated
surfaces, which makes it hard to review and still leaves new helper code
moving legacy policies around. This PR is a narrower alternative:
migrate only the exec-side Windows sandbox plumbing so the review can
focus on one production path and one compatibility boundary.
The goal is to stop threading `SandboxPolicy` through exec code without
expanding the migration into app-server, protocol, telemetry, config, or
session behavior.
## What changed
- Removed `ExecRequest::compatibility_sandbox_policy()`.
- Changed the Windows restricted-token and elevated filesystem override
helpers to accept `PermissionProfile` plus the split filesystem/network
policies instead of a `SandboxPolicy`.
- Kept the remaining legacy projection local to the writable-root
comparison that still needs to compare split policy behavior against the
legacy Windows backend model.
- Rejected restricted split filesystem policies that still grant
full-disk writes before using the Windows restricted-token backend,
preserving the previous clear-failure behavior for profiles that project
to `ExternalSandbox`.
- Updated the Windows sandbox override tests to exercise the new call
shape and cover the full-write split-profile regression.
## Verification
- `just test -p codex-core windows_restricted_token`
- `just test -p codex-core windows_elevated`
Fixes#26025.
## Why
`/goal edit` opens `CustomPromptView`, which did not use the paste-burst
handling that protects the main composer when terminals deliver paste as
rapid key events. On Windows terminals, the first pasted newline could
be treated as Enter-to-submit, truncating the goal edit and leaving the
rest of the paste behind.
## What
This reuses `PasteBurst` in `CustomPromptView` as a lightweight
Enter-suppression detector for paste-like key streams. Characters still
insert directly, explicit paste still goes through the view paste path,
and ordinary text entry still submits on Enter.
Skip turn git metadata enrichment when a turn has remote or multiple
executors, so we do not report the orchestrator checkout as executor
workspace metadata.
Test: `just test -p codex-core` (blocked by existing
`Session::conversation_id` compile error in `close_agent.rs`).
## Why
#23764 removed Windows resource stamping from `codex-windows-sandbox`,
but it also removed the setup helper's UAC manifest. That manifest was
doing more than cosmetic version metadata: Microsoft documents
`requestedExecutionLevel level="asInvoker"` as the setting that makes an
executable run at the same permission level as the process that started
it:
https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#trustinfo
In the reported session, `codex-windows-sandbox-setup.exe` was launched
for a non-elevated setup refresh and `CreateProcess` failed with `os
error 740` (`The requested operation requires elevation`). Restoring an
explicit `asInvoker` manifest records the helper's intended default
launch contract: normal launches inherit the caller's token, and
elevation only happens through the code paths that request it
explicitly.
The setup helper has two launch modes:
- setup refresh uses a normal `Command::new(...)` spawn and should never
trigger UAC
- full setup explicitly uses `ShellExecuteExW` with the `runas` verb
when elevation is required
Restoring `asInvoker` keeps refresh non-elevated by default while
preserving the explicit elevated path for full setup.
## What changed
- Restored a minimal `codex-windows-sandbox-setup.manifest` containing
only `requestedExecutionLevel level="asInvoker"`.
- Added a small build script that passes setup-helper-scoped manifest
linker args for MSVC and the Windows GNU/LLVM target used by Bazel.
- Wired the manifest into Bazel build-script data.
This does not restore `winres`, `FileDescription`, `ProductName`, or
package-wide resource stamping, so other Codex binaries that link
`codex-windows-sandbox` do not inherit metadata from this package.
## Verification
- `cargo fmt -p codex-windows-sandbox`
- `cargo build -p codex-windows-sandbox --bin
codex-windows-sandbox-setup`
- `cargo build -p codex-windows-sandbox --bin codex-command-runner`
- `cargo build -p codex-windows-sandbox --lib`
- Build-script output simulation for `CARGO_CFG_TARGET_ENV=msvc` emits
`/MANIFEST:EMBED` and `/MANIFESTINPUT:<manifest>`.
- Build-script output simulation for `CARGO_CFG_TARGET_ENV=gnu` +
`CARGO_CFG_TARGET_ABI=llvm` emits `-Wl,-Xlink=/manifest:embed` and
`-Wl,-Xlink=/manifestinput:<manifest>`.
- Inspected the built binaries and confirmed:
- `codex-windows-sandbox-setup.exe` contains `requestedExecutionLevel` /
`asInvoker`
- `codex-command-runner.exe` does not contain those manifest strings
- Windows `VersionInfo` remains blank for `FileDescription` /
`ProductName`
- `just test -p codex-windows-sandbox` ran through Nextest, with 114
passing, 2 skipped, and 1 existing Windows sandbox failure:
`unified_exec::tests::legacy_non_tty_cmd_emits_output` fails with
`CreateRestrictedToken failed: 87`.
## Why
The skills extension needs a real turn-time path before host, executor,
or remote skills can be routed through it. The previous code was mostly
a placeholder catalog/provider sketch, so there was no bounded
available-skills fragment, no source-owned `SKILL.md` read, and no place
for warnings or per-turn selection state to live.
This PR makes `ext/skills` the authority-preserving flow for listing
candidate skills and injecting only explicitly selected main prompts,
without adding more of that logic to `codex-core`.
## What changed
- Expands catalog entries with `main_prompt`, display path, short
description, dependency metadata, enabled/prompt visibility flags, and
authority/package-aware read requests.
- Replaces the placeholder `providers/*` modules with
`SkillProviderSource` and `SkillProviders`, routing list/read/search
calls by source kind and surfacing provider failures as warnings.
- Adds bounded available-skills rendering and `SKILL.md` main-prompt
truncation before the fragments enter model context.
- Resolves explicit skill selections from structured `UserInput::Skill`,
skill-file mentions, `skill://...` paths, and plain `$skill` text
mentions, then reads selected prompts through their owning provider.
- Stores mutable per-thread skills config and per-turn
catalog/selection/warning state.
- Adds `install_with_providers` so tests and future host wiring can
supply concrete providers.
## Testing
- Not run locally.
- Added `codex-rs/ext/skills/tests/skills_extension.rs` coverage for
available-catalog injection, selected prompt injection through the
owning provider, and prompt-hidden skills that remain invokable.
## Why
Goal progress accounting can be reached from multiple completion paths
for the same thread. Each path takes a progress snapshot, writes the
usage delta, and then marks that snapshot as accounted. When two
tool-completion hooks run at the same time, they can both observe the
same unaccounted delta and charge it twice.
## What changed
- Added a per-thread progress-accounting permit to
`GoalAccountingState`.
- Held that permit across the snapshot/write/mark-accounted critical
section for active-turn, idle, and tool-finish accounting.
- Added regression coverage for parallel tool-finish hooks so a shared
token delta is charged once and only one progress event is emitted.
## Testing
- Not run locally.
- Added `parallel_tool_finish_accounts_active_goal_progress_once`.
## Why
The skills extension needs the resolved turn environments to build a
real per-turn `SkillListQuery`. The previous `TurnLifecycleContributor`
hook only had a turn id, so it could only seed a placeholder query and
never carry the executor authorities that executor-scoped skill routing
will need.
Moving catalog resolution onto `TurnInputContributor` puts the skills
extension on the same turn-preparation path that already has the
environment ids and working directories for the submitted turn, while
keeping the actual prompt injection work for follow-up changes.
## What changed
- switch `ext/skills` from `TurnLifecycleContributor` to
`TurnInputContributor`
- build `executor_authorities` from `TurnInputContext.environments` and
pass them through `SkillListQuery`
- keep storing the resolved catalog in `SkillsTurnState`, but drop the
placeholder query helper that no longer matches the real data flow
- update the extension TODOs to reflect that per-turn catalog resolution
now happens in the turn-input contributor, and that prompt/context
injection still needs to move later
## Testing
- Not run locally.
## Why
`close_agent` is a parent-owned coordination tool: a worker should
return its result, then let its parent decide when to close it. Before
this change, if an MAv2 worker targeted itself, the resolved target
could flow through the normal close path and ask the agent control layer
to close the current conversation.
## What changed
- Reject `close_agent` when the resolved target is the current session's
`conversation_id`, returning a model-visible error that tells the worker
to return its result instead.
- Keep the guard after target resolution so it covers both thread-id
targets and task-path targets.
- Add coverage for self-targeting by thread id and by task name in
`multi_agents_tests.rs`.
Relevant code:
-
[`handle_close_agent`](https://github.com/openai/codex/blob/7c24e6641b693a3eed933dd376ce8f424ab6ea5f/codex-rs/core/src/tools/handlers/multi_agents_v2/close_agent.rs#L39-L57)
- [`multi_agent_v2_close_agent_rejects_self_target_by_id` /
`multi_agent_v2_close_agent_rejects_self_target_by_task_name`](https://github.com/openai/codex/blob/7c24e6641b693a3eed933dd376ce8f424ab6ea5f/codex-rs/core/src/tools/handlers/multi_agents_tests.rs#L3936-L4070)
## Testing
Not run locally.
## Why
`codex-core` currently owns the generic contextual-fragment trait and
several reusable fragment implementations. That makes it harder for
other crates to share the same host-owned model-input abstraction
without depending on all of `codex-core`.
This change extracts the reusable fragment machinery into a small
`codex-context-fragments` crate so future extension and skills work can
depend on the fragment abstraction directly.
## What Changed
- Added the `codex-context-fragments` crate with:
- `ContextualUserFragment`
- `FragmentRegistration` / `FragmentRegistrationProxy`
- additional-context fragment types
- Moved `SkillInstructions` into `codex-core-skills`, since
skill-specific rendering belongs with skills rather than generic core
context machinery.
- Kept `codex-core` re-exporting the fragment types it still uses
internally, so existing call sites keep the same shape.
- Updated Cargo and Bazel workspace metadata for the new crate.
## Verification
- `cargo metadata --locked --format-version 1 --no-deps`
- `just bazel-lock-update`
- `just bazel-lock-check`
## Why
Remote-control clients need to list and revoke controller-device grants
without enabling or enrolling the local relay. These are signed-in
account-management operations, so coupling them to websocket, pairing,
enrollment, or persisted relay state would prevent clients from managing
stale grants from the picker.
Related enhancement request: N/A. This adds the Codex app-server surface
for the planned upstream environment-scoped revoke endpoint.
## What Changed
- Added experimental app-server v2 RPCs:
- `remoteControl/client/list`
- `remoteControl/client/revoke`
- Added picker-oriented protocol types and standard generated schema
fixtures. The list response intentionally omits backend account id,
enrollment status, and location fields.
- Added `app-server-transport/src/transport/remote_control/clients.rs`
for environment-scoped GET and DELETE requests. It builds escaped URL
path segments, forwards optional pagination query fields, sends ChatGPT
auth plus `chatgpt-account-id`, converts RFC3339 `last_seen_at` values
to Unix seconds, accepts `204 No Content` revoke responses, and retries
once after a `401`.
- Extracted shared ChatGPT auth loading and recovery into
`app-server-transport/src/transport/remote_control/auth.rs` so
websocket, pairing, and client management use the same account-auth
boundary.
- Retained the configured remote-control base URL on
`RemoteControlHandle` and resolve management URLs lazily, preserving
deferred validation while relay startup is disabled.
- Registered list as `global_shared_read("remote-control-clients")` and
revoke as `global("remote-control-clients")`.
## Verification
- Added transport coverage proving list and revoke work while relay
state is disabled, IDs are escaped, picker-only fields are returned,
timestamps are converted, revoke accepts `204`, auth headers are
forwarded, `401` retries exactly once, `403` is not retried, and
malformed list payloads retain decode context.
- Added an app-server integration test proving both JSON-RPC methods
work before relay enablement and successful revoke returns `{}`.
- Regenerated and validated experimental and standard app-server schema
fixtures.
## Summary
Allow EDU ChatGPT workspaces to fetch cloud config bundles. The existing
cloud config eligibility gate only allowed business-like and enterprise
plans, which meant EDU admins could configure managed policies in the UI
but the Codex client would skip fetching them.
This keeps individual/pro and team-like usage-based plans excluded, and
adds service-level coverage for both `edu` and `education` plan aliases.
## Validation
- `just fmt`
- `just test -p codex-cloud-config`
- Built the Codex app locally, created a new EDU ChatGPT workspace, and
verified config bundles can be fetched and are properly applied.
## Disclaimer
Do not use for now
## Why
Extensions can already contribute prompt fragments and request same-turn
item injection, but there was no host-owned hook for contributing
structured `ResponseItem`s while Codex is assembling a new turn's
initial model input. This change adds that seam so extensions can attach
turn-local input that depends on the submitted user input and resolved
turn environments without routing through prompt text or late injection.
## What changed
- add `TurnInputContributor` to `codex_extension_api` and export the new
`TurnInputContext` / `TurnInputEnvironment` types it receives
- teach `ExtensionRegistry` to register and expose turn-input
contributors alongside the existing extension hooks
- call registered turn-input contributors from
`core/src/session/turn.rs` while building the initial injected input for
a turn, then append their returned `ResponseItem`s after the skill and
plugin injections
## Why
`PermissionProfile` is becoming the default way to represent Codex
permissions, but the implicit default behavior should stay the same for
now:
- trusted projects use `:workspace`
- untrusted projects also use `:workspace`
- roots without a trust decision use `:read-only`
- unsandboxed Windows falls back to `:read-only`
This keeps the existing sandbox semantics while making silent config
defaults observable as built-in permission profiles instead of treating
the legacy `SandboxPolicy` projection as the primary shape.
## What Changed
- Refactored legacy sandbox derivation to resolve the configured sandbox
mode once, then apply the implicit project fallback only when no sandbox
mode was configured.
- Preserved the existing trust-decision fallback: trusted and untrusted
projects default to workspace-write where supported.
- Added empty-config coverage asserting that an untrusted project
resolves to the built-in active permission profile (`:workspace` outside
unsandboxed Windows).
## Verification
- `just fmt`
- `just test -p codex-core 'config::'`
- `just test -p codex-config`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/25926).
* __->__ #25926
## Disclaimer
This is only here for iteration purpose! Do not make any code rely on
this
## Why
Skills still live behind `codex-core` discovery and injection paths, but
the extension system needs an authority-aware home before that logic can
move. This adds that boundary without changing current skills behavior,
and keeps host, executor, and remote skills distinct so future
list/read/search flows do not collapse back to ambient local paths.
## What changed
- Add the `codex-skills-extension` workspace/Bazel crate under
`ext/skills`.
- Define the initial catalog, authority, provider, and turn-state types
for authority-bound skill packages and resources.
- Register placeholder thread/config/prompt/turn lifecycle contributors
plus host, executor, and remote provider aggregation points.
- Capture the remaining extraction work as TODOs, including the missing
extension API hooks needed for per-turn catalog construction and typed
skill injection.
- Keep plugins outside the runtime skills model: plugin-installed skills
are treated as materialized host-owned skill sources once available.
## Verification
- Not run locally.
## Summary
- stop publishing Python runtime wheels as a side effect of Rust
releases
- publish runtime wheels from the Python SDK release workflow, either
explicitly before updating the SDK pin or immediately before a
`python-v*` SDK release
- resolve the runtime release from the requested version or the SDK
package's exact `openai-codex-cli-bin` pin
- build two musllinux-tagged wheels from the Rust-release Linux package
archives alongside the six existing runtime wheels
- validate SDK beta tags before any PyPI write
## Release configuration
- update the `openai-codex-cli-bin` PyPI trusted publisher to trust
`.github/workflows/python-sdk-release.yml` and the
`publish-python-runtime` job
## Pin update flow
- run the `python-sdk-release` workflow manually with the new runtime
version before opening or updating the SDK pin PR
- after the pin lands, a `python-v*` SDK tag republishes with
`skip-existing: true` before publishing the SDK package
## Validation
- ran `just fmt`
- validated the edited workflow YAML
- validated the embedded `publish-python-runtime` Bash with `bash -n`
- validated manual `0.136.0 -> rust-v0.136.0` mapping
- validated tag-driven `python-v0.1.0b3 -> 0.132.0 -> rust-v0.132.0`
mapping
- validated rejection of an invalid SDK tag before publication
- confirmed `rust-v0.136.0` contains the two required Linux package
archives
- CI will provide the full test signal
## Why
Standalone image generation remained top-level-only in code-mode
sessions.
## What changed
- Change imagegen exposure from `DirectModelOnly` to `Direct`.
- Keep direct-mode access while enabling nested code-mode access.
- Add a focused regression test for the exposure contract.
## Validation
- `just test -p codex-image-generation-extension`
## Why
`profile_sandbox_mode` was left over from the old selected legacy
profile path. Production now always derives permissions without that
value, and legacy profile contents are ignored, so keeping a parameter
that is always `None` makes `derive_permission_profile` look like it
still supports a fallback that no longer exists.
## What Changed
- Removed the `profile_sandbox_mode` argument from
`ConfigToml::derive_permission_profile`.
- Updated the production caller and legacy sandbox-policy test helper to
match.
- Dropped the stale unselected legacy-profile sandbox test that only
protected the removed fallback shape.
## Verification
- `just test -p codex-config`
- `just test -p codex-core 'config::'`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/25943).
* #25926
* __->__ #25943
## 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`
## Why
`code_mode_only` moved ordinary runtime tools behind `exec`, but it also
hid hosted Responses tools. Hosted `web_search` and `image_generation`
do not have a nested `exec` runtime path, so code-only sessions lost
those capabilities entirely even when their existing provider, auth,
model, and configuration gates passed.
## What changed
- Keep hosted Responses tools top-level in `code_mode_only` sessions
after their existing gates pass.
- Preserve the existing nested-tool behavior for ordinary runtimes and
the direct-only behavior for multi-agent v2 tools.
- Add planner coverage for `code_mode_only` with default multi-agent v2
settings, hosted live web search, and hosted image generation.
## Verification
- Added focused regression coverage in
`codex-rs/core/src/tools/spec_plan_tests.rs`.
- Left execution to CI per repository workflow.
## Summary
- Splits the monolithic `codex-cloud-config` implementation into focused
modules.
- Keeps behavior unchanged from the preceding config bundle runtime
switch.
## Details
This is the reviewability follow-up after the lineage-preserving
migration PRs. The split separates backend transport, loader
construction, cache handling, metrics, validation, service
orchestration, and focused tests into named files.
Verification: `just fmt`; `just test -p codex-cloud-config`.
## 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`