## Summary
- add experimental `thread/search` for local rollout-backed thread
search using `rg` over JSONL rollouts
- return search-specific result rows with optional previews instead of
storing preview data on `StoredThread` or ordinary `Thread` responses
- keep `thread/list` separate from full-content search and document the
new app-server surface
## Testing
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server
thread_search_returns_content_and_title_matches -- --nocapture`
## Why
Users reported that the replacement confirmation feels unnecessary when
the current thread goal is already complete. In that state, `/goal
<objective>` is starting fresh rather than interrupting active work.
## What changed
`/goal <objective>` now skips the replace confirmation when the existing
goal has `complete` status and uses the existing fresh replacement path.
Goals that are active, paused, blocked, usage-limited, or budget-limited
still require confirmation before being replaced.
## Summary
- replace the one-shot lazy remote exec-server cache with a
lock-protected current client
- when the cached websocket client is already disconnected, create one
fresh websocket client/session on the next `get()`
- keep existing disconnect failure behavior for old process sessions and
HTTP body streams; do not add session resume or request retry
## Why
The prior PR direction was trying to grow into session restore: resume
the old `session_id`, preserve existing process handles, and add
reconnect retry policy. That is more machinery than we want for this
slice.
For now, the useful minimum is simpler: later fresh remote operations
should not be stuck behind a dead cached websocket client, but anything
already attached to the dead connection should fail loudly through the
existing disconnect path. The server already has detached-session
cleanup via its existing TTL, so this PR does not need to add
client-side session preservation.
## What Changed
- `LazyRemoteExecServerClient::get()` now keeps the current concrete
client in a small mutex-protected cache plus one async connect lock.
- If that cached client is still connected, `get()` returns it.
- If that cached websocket client has observed the transport close,
`get()` creates a brand-new websocket client with a brand-new
exec-server session and replaces the cache.
- If that cached client is stdio-backed, behavior stays one-shot: the
dead client is returned and later work surfaces the existing disconnect
error.
- No `resume_session_id`, backoff, request replay, or existing
`RemoteExecProcess` rebinding is added here.
- Added focused websocket coverage that proves two concurrent `get()`
calls after disconnect share one fresh replacement client/session.
## Why
When a user runs `/goal` in a temporary session, the TUI can currently
surface an internal app-server failure such as `thread/goal/get failed
in TUI`. That message is technically true, but it does not explain the
actual constraint: goals require a saved session because goal state is
persisted with the thread.
This is especially confusing when `codex doctor` reports the background
app-server as running in ephemeral mode, since that wording is easy to
conflate with ephemeral thread/session behavior.
## What changed
- Added a TUI-side formatter for thread-goal RPC failures in
`codex-rs/tui/src/app/thread_goal_actions.rs`.
- Detects app-server/core errors that indicate goals are unsupported for
an ephemeral thread/session.
- Replaces the internal RPC failure with a user-facing explanation:
```text
Goals need a saved session. This session is temporary.
Run `codex` to start a saved session, or `codex resume` / `/resume` to reopen one.
```
- Preserves the existing generic failure wording for non-ephemeral goal
errors.
## Verification
- `cargo test -p codex-tui thread_goal_error_message --lib`
I also tried `cargo test -p codex-tui`; it built successfully but the
test runner aborted in an unrelated side-thread stack overflow
(`app::tests::discard_side_thread_removes_agent_navigation_entry`),
which reproduced when run by itself.
## Why
Installing `@openai/codex` currently places a Dotslash `rg` manifest at
`node_modules/@openai/codex/bin/rg`, even though the native optional
dependency already ships the actual helper under
`vendor/<target>/codex-path/rg`. The launcher prepends that `codex-path`
directory, so the top-level `bin/rg` file is redundant in the npm
install.
The remaining direct consumers of the manifest are package-building
paths: `scripts/codex_package/ripgrep.py` and
`codex-cli/scripts/install_native_deps.py`. Keeping the manifest under
`codex-cli/bin` makes it look like a shipped npm binary, so this moves
it next to the package-builder code that owns it. The checked-in
`@openai/codex` package metadata should likewise describe only the meta
package payload; generated platform packages continue to publish
`vendor`.
## What Changed
- Moved the Dotslash ripgrep manifest from `codex-cli/bin/rg` to
`scripts/codex_package/rg`.
- Updated the package builder, npm native-artifact hydrator, README, and
CLI help text to reference the new manifest location.
- Stopped `codex-cli/scripts/build_npm_package.py` from copying `rg`
into the `@openai/codex` meta package.
- Narrowed the checked-in meta package `files` whitelist to
`bin/codex.js`.
## Verification
- `python3 -m unittest discover -s scripts/codex_package -p "test_*.py"`
- `python3 -m unittest discover -s codex-cli/scripts -p "test_*.py"`
- `python3 -m py_compile codex-cli/scripts/build_npm_package.py
codex-cli/scripts/install_native_deps.py
scripts/codex_package/ripgrep.py scripts/codex_package/cli.py
scripts/stage_npm_packages.py`
- `codex-cli/scripts/build_npm_package.py --package codex --version
0.0.0-test --pack-output <tmp>/codex-meta-no-vendor.tgz`
- `tar -tf <tmp>/codex-meta-no-vendor.tgz` showed only
`package/bin/codex.js`, `package/package.json`, and `package/README.md`.
- Direct staging check showed `codex` uses `files: ["bin/codex.js"]`
while `codex-darwin-arm64` still uses `files: ["vendor"]`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23833).
* #23836
* __->__ #23833
## Why
The named-profile `/permissions` picker needs a small TUI action path
that can select permission profiles without folding the menu UI and
profile metadata into the same review.
## What changed
- Carry permission-profile selections through the TUI app event flow.
- Persist selected profiles while preserving the existing approval
settings and guardrail prompts.
- Keep the legacy `/permissions` picker behavior in this layer; the
profile-mode menu stays in the follow-up PR.
## Stack
1. [#22931](https://github.com/openai/codex/pull/22931):
runtime/session/network propagation for active permission profiles.
2. **This PR**: TUI selection plumbing and guardrail flow.
3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
`/permissions` menu and custom profile display.
<img width="1632" height="1186" alt="image"
src="https://github.com/user-attachments/assets/69ddcd5e-b57c-468d-8c1d-246916323c15"
/>
## Validation
- `git diff --cached --check` before commit.
- Full test run skipped at the user request while pushing the split
stack.
## Why
[#23883](https://github.com/openai/codex/pull/23883) moved the
user-facing `--profile` flag onto profile v2. The shared CLI option
layer still carried the old `config_profile` slot and several CLI
entrypoints still copied that value into legacy config overrides.
Leaving that path around makes the CLI surface look like it still
selects legacy `[profiles.*]` state even though `--profile` now means
`$CODEX_HOME/<name>.config.toml`.
## What
- Remove the legacy `config_profile` field and merge/copy path from
[`SharedCliOptions`](95baaf7292/codex-rs/utils/cli/src/shared_options.rs (L8-L177)).
- Stop forwarding profile-v1 overrides from CLI, exec, TUI, doctor,
debug, feature, and exec-server paths; runtime profile selection remains
on `config_profile_v2` through
[`loader_overrides_for_profile`](95baaf7292/codex-rs/cli/src/main.rs (L1606-L1619)).
- Resolve local OSS provider selection from the base config in exec and
TUI now that the legacy profile argument is gone.
## Testing
- Not run (cleanup-only follow-up to #23883).
## Summary
- route each configured MCP server through an explicit per-server
`environment_id` instead of a manager-wide remote toggle
- default omitted `environment_id` to `local`, resolve named ids through
`EnvironmentManager`, and fail only the affected MCP server when an
explicit id is unknown
- keep local stdio on the existing local launcher path for now, while
named-environment stdio uses the selected environment backend and
requires an absolute `cwd`
- allow local HTTP MCP servers to keep using the ambient HTTP client
when no local `Environment` is configured; named-environment HTTP MCPs
use that environment's HTTP client
## Validation
- devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
//codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
//codex-rs/rmcp-client:test_streamable_http_server`
- devbox app-server config matrix with real `config.toml` /
`environments.toml` files covering omitted local, explicit local,
omitted local under remote default, explicit remote stdio, local HTTP
without local env, explicit remote HTTP, local stdio without local env,
unknown explicit env, and remote stdio without `cwd`
## Why
Profile v2 is taking over the user-facing profile selection path, so the
CLI no longer needs to expose the transitional `--profile-v2` spelling.
This switches the public args surface to `--profile` before the
remaining legacy profile plumbing is removed separately.
## What
- Rebind `--profile` and `-p` to the v2 profile name argument that
selects `$CODEX_HOME/<name>.config.toml`.
- Stop parsing the legacy shared CLI profile argument while keeping its
implementation path in place for follow-up cleanup.
- Update CLI validation, profile-name parse errors, and the
legacy-profile collision message/tests to refer to `--profile`.
## Testing
- `cargo test -p codex-cli -p codex-config -p codex-protocol -p
codex-utils-cli`
## Why
Tool exposure is a planning concern, but the deferred MCP path and
dispatch-only legacy shell path were carrying those decisions in handler
constructors and a shell-only tool-family builder. Keeping those
decisions in `spec_plan` makes the core tool plan easier to follow and
keeps handlers focused on runtime behavior.
## What changed
- add `PlannedTools` helpers for ordinary runtimes, exposure overrides,
dispatch-only runtimes, and hosted specs
- inline shell tool assembly into `core/src/tools/spec_plan.rs` and
remove the shell-only `tool_family` module
- remove exposure state and special exposure constructors from
`McpHandler` and `ShellCommandHandler`
- keep hidden runtime behavior centralized in `ExposureOverride`,
including disabling parallel tool calls for hidden handlers
## Testing
- Not run (refactor only)
## What
Remove the exact captured request-count assertion from the
`SubagentStart` hook integration test while still waiting for the child
request that matches the injected hook context.
## Why
The test owns the start-hook behavior and already verifies that the
child request reaches the context matcher plus that the start/session
hook logs have the expected invocations. Counting every request captured
by the response mock makes the test sensitive to lifecycle timing
outside that contract and has been flaky in CI.
## Testing
- `cargo test -p codex-core --test all
suite::subagent_notifications::subagent_start_replaces_session_start_and_injects_context
-- --exact`
## Why
`ToolExecutor` is the runtime contract that keeps a callable tool and
its model-visible spec together. Leaving `spec()` optional lets a
registered runtime silently omit that half of the contract, and it also
overloads a missing spec as an exposure decision for tools that should
stay dispatchable without being shown to the model.
## What
- Make `ToolExecutor::spec()` required and update core, extension, and
test tool executors to return a concrete `ToolSpec`.
- Add `ToolExposure::Hidden` for dispatch-only tools. The legacy
`shell_command` runtime in unified-exec sessions now uses that explicit
exposure instead of hiding itself by omitting a spec.
- Build MCP tool specs when `McpHandler` is constructed so invalid MCP
specs are skipped before the handler is registered.
- Keep tool planning aligned with the new contract for direct, deferred,
hidden, code-mode, dynamic, and namespaced tool paths.
## Testing
- Added tool-plan coverage that invalid MCP tool specs are not
registered.
- Updated shell-family coverage for the hidden legacy `shell_command`
runtime and the affected tool executor test fixtures.
## Why
Remote compaction now has two implementations: the existing
server-rebuilt v1 path and the newer client-rebuilt v2 path behind
`remote_compaction_v2`. The v1 path bounds retained
user/developer/system history before installing the compaction item,
while v2 was previously carrying the full retained history forward. That
made the two paths diverge for large pre-compaction transcripts even
though they are meant to preserve the same compaction contract.
This aligns v2 with the retained-history budget expected from v1 so
switching the feature flag does not materially change which
pre-compaction messages survive into the rebuilt history.
## What changed
- Apply a retained-message character budget while rebuilding v2
compacted history in `core/src/compact_remote_v2.rs`.
- Keep newest retained messages first, truncate the boundary message
with the shared `truncate_text(...)` helper, and drop older retained
messages once the budget is exhausted.
- Preserve non-text retained message content such as images while
truncating text content.
- Use the current `64_000` token retained-message default translated to
the existing `4x` character budget.
## Testing
- `cargo test -p codex-core compact_remote_v2::tests::`
- Added focused coverage for newest-first retention and truncating
multipart retained messages without dropping images.
## What
- Add a small extension capability for injecting model-visible response
items into the active turn
- Have the goal extension inject hidden goal-context steering when
tool-finish accounting reaches `BudgetLimited`
- Cover the extension backend path with an assertion on the injected
steering item
## Why
PR #23696 persists and emits the budget-limited goal update from
tool-finish accounting, but it leaves the model unaware of that
transition. The existing core runtime steers the model to wrap up in
this case; the extension path should do the same through an explicit
host capability.
## Testing
- `just fmt`
- `cargo test -p codex-goal-extension`
- `cargo test -p codex-extension-api`
## Why
`prewarm_websocket` intentionally stays out of rollout inference
tracing, but the next traced websocket request can still reuse the
warmup `response_id` and send an empty `input` delta. If tracing records
that wire payload verbatim, replay sees an incremental request whose
parent was never traced and cannot reconstruct the conversation.
This fixes that at the producer boundary instead of relaxing
`rollout-trace` replay semantics around unresolved
`previous_response_id` values.
## What
- track whether the last websocket response came from an untraced warmup
and clear that state when the websocket session is reset or reconnected
- when a traced websocket request reuses that warmup parent, keep
sending the compressed websocket request on the wire but record the
logical `ResponsesApiRequest` in the rollout trace
- add a regression test that proves replay reconstructs the logical user
message even though the websocket follow-up carries
`previous_response_id = warm-1` with empty `input`
- update `InferenceTraceAttempt::record_started` docs to reflect that
callers may record a logical request rather than the exact transport
payload
## Testing
- `cargo test -p codex-core --test all
responses_websocket_request_prewarm_traces_logical_request`
## Why
The Python and TypeScript SDKs launch the native Codex runtime directly,
so they need to consume the same package artifact shape that release
jobs now produce. The runtime wheel should be built from the canonical
Codex package archive rather than reconstructing a parallel layout from
loose binaries.
## What Changed
- Stage `openai-codex-cli-bin` by extracting
`codex-package-<target>.tar.gz` into `src/codex_cli_bin` and validating
the expected package layout.
- Update release workflows to pass the generated package archive into
`stage-runtime` instead of the temporary package directory.
- Update Python runtime setup to download `codex-package-*.tar.gz`
release assets directly.
- Expose Python runtime helpers for the bundled package directory and
`codex-path`, and prepend that path when `openai_codex` launches the
installed runtime without duplicating Windows `Path`/`PATH` keys.
- Teach the TypeScript SDK to resolve package-layout optional
dependencies while keeping the existing npm fallback layout, and
preserve the existing Windows path variable casing when prepending
`codex-path`.
## Test Plan
- `python3 -m py_compile sdk/python/scripts/update_sdk_artifacts.py
sdk/python/_runtime_setup.py sdk/python/src/openai_codex/client.py
sdk/python-runtime/src/codex_cli_bin/__init__.py`
- `uv run --frozen --project sdk/python --extra dev ruff check
sdk/python/scripts/update_sdk_artifacts.py sdk/python/_runtime_setup.py
sdk/python/src/openai_codex/client.py
sdk/python/tests/test_artifact_workflow_and_binaries.py
sdk/python-runtime/src/codex_cli_bin/__init__.py`
- `uv run --frozen --project sdk/python --extra dev pytest
sdk/python/tests/test_artifact_workflow_and_binaries.py`
- `pnpm eslint src/exec.ts tests/exec.test.ts`
- `pnpm test --runInBand tests/exec.test.ts`
## Why
This is the functional handoff PR for the Windows sandbox
`PermissionProfile` migration. After #23714, the Windows elevated
backend can accept a profile-native request, but core still sent a
compatibility `SandboxPolicy` into the elevated command-runner path.
That meant profile-only details such as deny globs had to be translated
through side channels instead of being preserved in the runner
`SpawnRequest`.
Passing the real `PermissionProfile` completes the command-runner
handoff while leaving the unelevated restricted-token fallback on the
legacy policy-string API.
## What
- Updates one-shot Windows elevated execution in `core/src/exec.rs` to
call `run_windows_sandbox_capture_for_permission_profile_elevated`.
- Updates unified exec in `core/src/unified_exec/process_manager.rs` to
call `spawn_windows_sandbox_session_elevated_for_permission_profile`.
- Passes `request.permission_profile` /
`exec_request.permission_profile` and the stored Windows sandbox policy
cwd to the elevated backend.
- Keeps compatibility `SandboxPolicy` serialization only for the
non-elevated restricted-token fallback.
## Verification
- `cargo test -p codex-core --test all --no-run`
## Why
Cloud-managed `requirements.toml` should be able to define the managed
permission profiles a client may select and constrain that selectable
set without requiring local user config to recreate the profile catalog.
This keeps requirements focused on restrictions. The selected default
remains a config or session choice, while requirements contribute the
managed profile bodies and `allowed_permissions` allowlist that the
config-loading boundary validates before a resolved runtime
`PermissionProfile` is installed.
## What changed
- Add `requirements.toml` support for a managed permission-profile
catalog plus its allowlist:
```toml
allowed_permissions = ["review", "build"]
[permissions.review]
extends = ":read-only"
[permissions.build]
extends = ":workspace"
```
- Merge requirements-defined profile bodies into the effective
permission catalog and reject profile ids that collide with
config-defined profiles.
- Validate that every `allowed_permissions` entry resolves to a built-in
or catalog profile before selection uses it.
- Preserve allowed configured named-profile selections. When a
configured named profile is disallowed, fall back to the first allowed
requirements profile with a startup warning.
- Keep built-in selections and the stock trust-based `:read-only` /
`:workspace` fallback path intact when no permission profile is
explicitly selected.
- Centralize the managed catalog and allowlist selection path in
`EffectivePermissionSelection` so the requirements boundary is visible
in config loading.
- Surface `allowedPermissions` through `configRequirements/read`, and
update the generated app-server schema fixtures plus the app-server
README.
## Validation
- `cargo test -p codex-config`
- `cargo test -p codex-core system_requirements_`
- `cargo test -p codex-core system_allowed_permissions_`
- `cargo test -p codex-app-server-protocol`
- `just write-app-server-schema`
## Related work
- Uses merged permission-profile inheritance support from #22270 and
#23705.
- Kept separate from the in-flight permission profile listing API in
#23412.
## Why
This is the next step after #23167 in the Windows sandbox
`PermissionProfile` migration. The elevated Windows backend still
exposed policy-string entry points, which forced callers to pass a
compatibility `SandboxPolicy` before the command-runner IPC could
receive a profile.
Adding profile-native APIs first keeps the core switch in the next PR
small: reviewers can see that the Windows crate can prepare elevated
setup, capability SIDs, and runner IPC from a resolved
`PermissionProfile` without changing core behavior yet.
## What
- Adds `ElevatedSandboxProfileCaptureRequest` and
`run_windows_sandbox_capture_for_permission_profile_elevated` for
one-shot elevated capture.
- Adds `spawn_windows_sandbox_session_elevated_for_permission_profile`
for unified exec sessions.
- Factors elevated spawn prep through
`prepare_elevated_spawn_context_for_permissions`, so both new APIs
operate from `ResolvedWindowsSandboxPermissions` directly.
- Keeps the existing legacy policy-string APIs as adapters for callers
that have not moved yet.
## Verification
- `cargo test -p codex-windows-sandbox`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23714).
* #23715
* __->__ #23714
## Why
If a user configures `approval_policy = "never"` with `sandbox_mode =
"danger-full-access"`, managed requirements can reject full access and
force the existing permission fallback to read-only. That leaves Codex
in a dead-end session: writes are blocked by the sandbox, while
approvals are disabled so the session cannot ask to proceed.
This PR rejects that constrained configuration during startup instead of
letting the TUI enter a read-only session that cannot make progress. The
rejection is attached to the requirement-constrained permission path in
[`Config`](39f0abc0a7/codex-rs/core/src/config/mod.rs (L3301-L3318)).
## What changed
- Reject the `danger-full-access` to read-only managed-requirements
fallback when the effective approval policy is `never`.
- Explain in the startup config error why the fallback is invalid and
how to fix it.
- Add a regression test for the managed requirements path.
## Stack
1. Parent PR: #18868 adds MITM hook config and model only.
2. Parent PR: #20659 wires hook enforcement into the proxy request path.
3. This PR changes the user facing PermissionProfile TOML shape.
## Why
1. The broader goal is to make MITM clamping usable from the same
permission profile that already controls network behavior.
2. This PR is the config UX layer for the stack. It moves MITM policy
into `[permissions.<profile>.network.mitm]` instead of exposing the flat
runtime shape to users.
3. The named hook and action tables belong here because users need
reusable policy blocks that are easy to review, while the proxy runtime
only needs a flat hook list.
4. This PR validates action refs during config parsing so mistakes in
the user facing policy fail before a proxy session starts.
5. Keeping the lowering here lets the proxy keep its simpler runtime
model and lets PermissionProfile remain the single source of network
permission policy.
## Summary
1. Keep MITM policy inside `[permissions.<profile>.network.mitm]` so the
selected PermissionProfile owns network proxy policy.
2. Use named MITM hooks under
`[permissions.<profile>.network.mitm.hooks.<name>]`.
3. Put host, methods, path prefixes, query, headers, body, and action
refs on the hook table.
4. Define reusable action blocks under
`[permissions.<profile>.network.mitm.actions.<name>]`.
5. Represent action blocks with `NetworkMitmActionToml`, then lower them
into the proxy runtime action config.
6. Reject unknown refs, empty refs, and empty action blocks during
config parsing.
7. Keep the runtime hook model unchanged by lowering config into the
existing proxy hook list.
8. Preserve the #20659 activation fix for nested MITM policy.
## Example
```toml
[permissions.workspace.network.mitm]
enabled = true
[permissions.workspace.network.mitm.hooks.github_write]
host = "api.github.com"
methods = ["POST", "PUT"]
path_prefixes = ["/repos/openai/"]
action = ["strip_auth"]
[permissions.workspace.network.mitm.actions.strip_auth]
strip_request_headers = ["authorization"]
```
## Validation
1. Regenerated the config schema.
2. Ran the core MITM config parsing and validation tests.
3. Ran the core PermissionProfile MITM proxy activation tests.
4. Ran the core config schema fixture test.
5. Ran the network proxy MITM policy tests.
6. Ran the scoped Clippy fixer for the network proxy crate.
7. Ran the scoped Clippy fixer for the core crate.
---------
Co-authored-by: Winston Howes <winston@openai.com>
Add owning plugin id to MCP tool call items so we can better filter them
at plugin level.
## Summary
- add optional `plugin_id` to MCP tool-call items and legacy begin/end
events
- propagate plugin metadata into emitted core items and app-server v2
`ThreadItem::McpToolCall`
- preserve plugin ids through app-server replay/redaction paths and
regenerate v2 schema fixtures
## Testing
- `just write-app-server-schema`
- `just fmt`
- `just fix -p codex-core`
- `cargo test -p codex-protocol -p codex-app-server-protocol`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core mcp_tool_call_item_includes_plugin_id --lib`
- `cargo check -p codex-tui --tests`
- `cargo check -p codex-app-server --tests`
- `git diff --check`
## Notes
- `just fix -p codex-core` completed with two non-fatal
`too_many_arguments` warnings on the touched MCP notification helpers.
- A broader `cargo test -p codex-core` run passed core unit tests, then
hit shell/sandbox/snapshot failures in the integration target.
- A broader app-server downstream run hit the existing
`in_process::tests::in_process_start_clamps_zero_channel_capacity` stack
overflow; `cargo test -p codex-exec` also hit the existing sandbox
expectation mismatch in
`thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile`.
## Why
#23752 and #23759 add Python unit tests for the Codex package builder,
but the root CI workflow did not run tests under
`scripts/codex_package`. That left the `zstd` resolution and
prebuilt-resource packaging behavior covered locally without a CI check.
## What changed
- Add a root CI step in `.github/workflows/ci.yml` that runs `python3 -m
unittest discover -s scripts/codex_package -p "test_*.py"`.
- Keep the step with the existing Python verification checks before
Node/pnpm setup.
## Verification
- `python3 -m unittest discover -s scripts/codex_package -p "test_*.py"`
- `python3 -m py_compile scripts/codex_package/*.py`
## Why
The `codex-windows-sandbox` crate was embedding Windows resource
metadata through a package-level `build.rs`. Because that package also
exposes the `codex_windows_sandbox` library, downstream binaries that
link the library could inherit `FileDescription` / `ProductName` values
of `codex-windows-sandbox`.
That made ordinary Codex binaries, including the long-lived `codex.exe`
app-server sidecar, appear as `codex-windows-sandbox` in Windows UI
surfaces such as Task Manager / file properties.
We do not rely on this metadata enough to justify a larger bin-only
resource split, so this removes the resource stamping entirely.
## What changed
- Removed the `windows-sandbox-rs` build script that invoked `winres`.
- Removed the setup manifest that was only consumed by that build
script.
- Removed the `winres` build dependency and corresponding `Cargo.lock` /
`MODULE.bazel.lock` entries.
- Removed the now-unused Bazel build-script data.
## Verification
- `cargo build -p codex-windows-sandbox --bins`
- `cargo build -p codex-cli --bin codex`
- `bazel mod deps --lockfile_mode=update` via Bazelisk, with local
remote-cache-disabling flags because `bazel` is not installed on PATH
here
- `bazel mod deps --lockfile_mode=error` via Bazelisk, with the same
local flags
- Verified rebuilt `codex.exe`, `codex-command-runner.exe`, and
`codex-windows-sandbox-setup.exe` now have blank `FileDescription` /
`ProductName` fields.
- `cargo test -p codex-windows-sandbox` still fails on two legacy
Windows sandbox tests with `CreateRestrictedToken failed: 87` and the
follow-on poisoned test lock; 85 passed, 2 ignored.
## Why
Realtime v1 websocket sessions now expect a slightly different boundary
shape for text input, completed input transcripts, and connection
headers. Codex was still using the older shape, so some v1 text appends
could be rejected before the existing conversation flow could handle
them.
## What changed
- Send v1 user text items with `input_text` content
- Accept v1 turn-marked input transcript events as completed transcripts
- Add the v1 alpha header only for v1 realtime sessions
- Cover the outbound text shape, transcript parsing, and versioned
headers
## Test plan
- `cargo test -p codex-api endpoint::realtime_websocket::methods::tests`
- `cargo test -p codex-core quicksilver_alpha_header`
## Why
Model catalog responses can now advertise a nullable
`default_service_tier` for each model. Codex needs to preserve three
distinct states all the way from config/app-server inputs to inference:
- no explicit service tier, so the client may apply the current model
catalog default when FastMode is enabled
- explicit `default`, meaning the user intentionally wants standard
routing
- explicit catalog tier ids such as `priority`, `flex`, or future tiers
Keeping those states distinct prevents the UI from showing one tier
while core sends another, especially after model switches or app-server
`thread/start` / `turn/start` updates.
## What Changed
- Plumbed `default_service_tier` through model catalog protocol types,
app-server model responses, generated schemas, model cache fixtures, and
provider/model-manager conversions.
- Added the request-only `default` service tier sentinel and normalized
legacy config spelling so `fast` in `config.toml` still materializes as
the runtime/request id `priority`.
- Moved catalog default resolution to the TUI/client side, including
recomputing the effective service tier when model/FastMode-dependent
surfaces change.
- Updated app-server thread lifecycle config construction so
`serviceTier: null` preserves explicit standard-routing intent by
mapping to `default` instead of internal `None`.
- Kept core responsible for validating explicit tiers against the
current model and stripping `default` before `/v1/responses`, without
applying catalog defaults itself.
## Validation
- `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
- `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
- `cargo test -p codex-tui service_tier`
- `cargo test -p codex-protocol service_tier_for_request`
- `cargo test -p codex-core get_service_tier`
- `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
service_tier`
## Why
The `goals` feature is ready to be available without requiring users to
opt into experimental features. Keeping it behind the beta flag leaves
persisted thread goals and automatic goal continuation disabled by
default.
This PR also marks the goal-related app server APIs and events as no
longer experimental.
## What changed
- Mark `goals` as `Stage::Stable`.
- Enable `goals` by default in `codex-rs/features/src/lib.rs`.
## Summary
- render `codex plugin list` as one table per marketplace with the
marketplace manifest path shown above each table
- surface the installed plugin version in the CLI output by threading
`installed_version` through marketplace listing state
- narrow the system-root exemption so only known bundled/runtime
marketplaces skip missing-manifest failures, and keep `VERSION` empty
for cached-but-unconfigured plugins
## Rationale
The plugin list UX was hard to scan as a flat list and did not show
which installed version was active. This change makes the CLI output
easier to read in the real multi-marketplace case, keeps the plugin path
visible, fixes the Sapphire regression where bundled/runtime marketplace
roots were blocking `plugin list`, and addresses the two review findings
that came out of the follow-up deep review.
## Key Decisions
- kept the CLI output grouped per marketplace instead of one global
table so the marketplace path can live with the rows it owns
- kept `VERSION` as the installed version, which means it is empty until
a plugin is actually installed
- handled the bundled/runtime regression in the CLI snapshot validation
path rather than widening app-server protocol or changing marketplace
loading behavior
- narrowed the exemption to known system marketplace names plus expected
system paths, so user-configured marketplaces under those directories
still fail loudly
- gated `installed_version` on actual installed state so `VERSION`
cannot show stale cache state for `not installed` rows
## Validation
- `just fmt`
- Sapphire: `cargo test -p codex-cli --test plugin_cli` (`14 passed; 0
failed`)
- Sapphire smoke test: bundled/runtime roots still work
- `cargo run -q -p codex-cli -- plugin add sample@debug`
- `cargo run -q -p codex-cli -- plugin list`
- verified the bundled/runtime-root scenario no longer errors and shows
the expected marketplace table output
- Sapphire smoke test: custom marketplace under bundled path still
errors
- verified `failed to load configured marketplace snapshot(s)` for
`custom-marketplace`
- Sapphire smoke test: cached-but-unconfigured plugin hides version
- verified `sample@debug not installed` renders with an empty `VERSION`
column
## Sample Output
```text
/tmp/custom-marketplace/plugin.json
NAME VERSION STATUS DESCRIPTION
sample@debug 1.0.0 enabled Debug sample plugin
other@local not installed Local development plugin
```
# What
<img width="1792" height="1024" alt="image"
src="https://github.com/user-attachments/assets/8f81d232-5813-4994-a61d-e42a05a93a3e"
/>
`SubagentStop` runs when a thread-spawned subagent turn is about to
finish. Thread-spawned subagents use `SubagentStop` instead of the
normal root-agent `Stop` hook.
Configured handlers match on `agent_type`. Hook input includes the
normal stop fields plus:
- `agent_id`: the child thread id.
- `agent_type`: the resolved subagent type.
- `agent_transcript_path`: the child subagent transcript path.
- `transcript_path`: the parent thread transcript path.
- `last_assistant_message`: the final assistant message from the child
turn, when available.
- `stop_hook_active`: `true` when the child is already continuing
because an earlier stop-like hook blocked completion.
`SubagentStop` shares the same completion-control semantics as `Stop`,
scoped to the child turn:
- No decision allows the child turn to finish.
- `decision: "block"` with a non-empty `reason` records that reason as
hook feedback and continues the child with that prompt.
- `continue: false` stops the child turn. If `stopReason` is present,
Codex surfaces it as the stop reason.
# Lifecycle Scope
Only thread-spawned subagents run `SubagentStop`.
Internal/system subagents such as Review, Compact, MemoryConsolidation,
and Other do not run normal `Stop` hooks and do not run `SubagentStop`.
This avoids exposing synthetic matcher labels for internal
implementation paths.
# Stack
1. #22782: add `SubagentStart`.
2. This PR: add `SubagentStop`.
3. #22882: add subagent identity to normal hook inputs.
## Why
Once a named permission profile is selected, runtime state has to keep
that profile identity intact instead of collapsing back to anonymous
effective permissions. The session refresh path also needs to rebuild
profile-derived network proxy state so active profile switches take
effect consistently.
## What changed
- Preserve the active permission profile through session updates.
- Rebuild profile-derived runtime/network configuration when the active
profile changes.
- Keep the runtime path aligned with the current session configuration
APIs.
- Tighten the affected tests, including the Windows delete-pending
memory-file case that was intermittently tripping CI.
## Stack
1. **This PR**: runtime/session/network propagation for active
permission profiles.
2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
plumbing and guardrail flow.
3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
`/permissions` menu and custom profile display.
<img width="1296" height="906" alt="image"
src="https://github.com/user-attachments/assets/077fa3a7-80cb-4925-80b1-d2395018d90a"
/>
## Why
This is the next step in the Windows sandbox migration away from the
legacy `SandboxPolicy` abstraction. #22923 moved write-root and token
decisions onto `ResolvedWindowsSandboxPermissions`, but setup and
identity still accepted `SandboxPolicy` and converted internally. This
PR pushes that conversion outward so the setup path consumes the
resolved Windows permission view directly.
## What Changed
- Changed `SandboxSetupRequest` to carry
`ResolvedWindowsSandboxPermissions` instead of `SandboxPolicy` plus
policy cwd.
- Updated setup refresh/elevation and identity credential preparation to
use resolved permissions for read roots, write roots, network identity,
and deny-write payload planning.
- Removed the production `allow.rs` legacy wrapper; allow-path
computation now takes resolved permissions directly.
- Added a permissions-based world-writable audit entry point while
keeping the existing legacy wrapper for compatibility.
- Updated legacy ACL setup and the core Windows setup bridge to
construct resolved permissions at the boundary.
- Hardened the Windows sandbox integration test helper staging so Bazel
retries can reuse an already-staged helper if a prior sandbox helper
process still has the executable open.
## Verification
- `cargo test -p codex-windows-sandbox`
- `cargo test -p codex-core --test all --no-run`
- `just fix -p codex-windows-sandbox`
- `just fix -p codex-core`
- Attempted `cargo check -p codex-windows-sandbox --target
x86_64-pc-windows-gnullvm`, but the local machine is missing
`x86_64-w64-mingw32-clang`; Windows CI should cover that target.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23167).
* #23715
* #23714
* __->__ #23167
## Why
Release packaging should be a staging step once release binaries have
already been built and signed. The Windows release job was downloading
and signing `codex-command-runner.exe` and
`codex-windows-sandbox-setup.exe`, but `scripts/build_codex_package.py`
still rebuilt those helpers while creating the package archives.
That makes the package step slower and, more importantly, risks putting
helper binaries in the archive that were produced after the signing
step. Linux had the same shape for package resources: `bwrap` could be
rebuilt by the package builder instead of being passed in as a prebuilt
release artifact.
This builds on #23752, which fixes `.tar.zst` creation when Windows
runners rely on the repository DotSlash `zstd` wrapper.
## What changed
- Add explicit prebuilt resource inputs to the Codex package builder:
- `--bwrap-bin`
- `--codex-command-runner-bin`
- `--codex-windows-sandbox-setup-bin`
- Make `.github/scripts/build-codex-package-archive.sh` pass resource
binaries from the release output directory when they are already
present.
- Build Linux `bwrap` for app-server release jobs too, so app-server
package creation does not invoke Cargo just to supply the package
resource.
- Keep macOS package creation as a no-Cargo path when `--entrypoint-bin`
is provided, since macOS packages have no resource binaries.
- Add unit coverage showing prebuilt macOS, Linux, and Windows package
inputs result in no source-built binaries.
## Verification
- `python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'`
- `python3 -m py_compile scripts/codex_package/*.py`
- `bash -n .github/scripts/build-codex-package-archive.sh`
- Dry-ran Linux and Windows package builds with fake prebuilt resources
and a nonexistent Cargo path to verify the package builder did not
invoke Cargo.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23759).
* #23760
* __->__ #23759
## Why
Linux release jobs build the MUSL artifacts that ship in Codex releases,
including both the primary CLI bundle and the app-server bundle. Those
builds should run on the Codex Linux runner pools instead of generic
Ubuntu-hosted runners so release builds use the x64 and arm64 capacity
intended for Codex artifacts.
## What Changed
- Moves the `x86_64-unknown-linux-musl` release matrix entries in
`.github/workflows/rust-release.yml` from `ubuntu-24.04` to
`codex-linux-x64-xl`.
- Moves the `aarch64-unknown-linux-musl` release matrix entries from
`ubuntu-24.04-arm` to `codex-linux-arm64`.
- Leaves macOS release jobs, target triples, bundle names, and artifact
names unchanged.
## Verification
- Reviewed the workflow matrix diff for
`.github/workflows/rust-release.yml`.
- Not run locally; this is a GitHub Actions runner configuration change.
## Why
This is the third PR in the Windows sandbox `SandboxPolicy` ->
`PermissionProfile` migration stack.
#22896 introduced `ResolvedWindowsSandboxPermissions`, and #22918 moved
elevated runner IPC to carry `PermissionProfile`. This PR starts moving
the remaining setup/spawn helpers away from asking legacy enum questions
like “is this `WorkspaceWrite`?” and toward resolved runtime permission
questions like “does this profile require write capability roots?”
## What changed
- Added resolved-permissions helpers for network identity and
write-capability detection.
- Moved setup write-root gathering to operate on
`ResolvedWindowsSandboxPermissions`, with the legacy `SandboxPolicy`
wrapper left in place for existing call sites.
- Updated identity setup, elevated capture setup, and world-writable
audit denies to use resolved write roots.
- Updated spawn preparation to carry resolved permissions in
`SpawnContext` and use them for network blocking, setup write roots,
elevated capability SID selection, and legacy capability roots.
- Removed a now-unused legacy write-root helper.
## Verification
- `cargo test -p codex-windows-sandbox`
- `just fix -p codex-windows-sandbox`
- Existing stack checks are green on #22896 and #22918; CI has started
for this PR.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22923).
* #23715
* #23714
* #23167
* __->__ #22923
## Why
The Windows release job installed DotSlash successfully, but package
archive creation still failed while writing `codex-package-*.tar.zst`.
The Python archiver used `shutil.which("zstd")`, which does not reliably
find the extensionless DotSlash manifest at `.github/workflows/zstd`
from native Windows Python.
That left release packaging dependent on a command named exactly `zstd`
being discoverable on `PATH`, even though the repository already carries
a DotSlash wrapper for Windows runners.
## What changed
- Add `resolve_zstd_command()` to prefer a real `zstd` binary when
present.
- Fall back to invoking `dotslash .github/workflows/zstd` when `zstd` is
not on `PATH`.
- Keep the error explicit when neither `zstd` nor the DotSlash fallback
is available.
- Add unit coverage for direct `zstd`, DotSlash fallback, and
missing-tool error paths.
## Verification
- `python3 -m unittest discover -s scripts/codex_package -p 'test_*.py'`
- `python3 -m py_compile scripts/codex_package/*.py`
## Stack
1. Parent PR: #18868 adds MITM hook config and model only.
2. This PR wires runtime enforcement.
3. User facing config follow up: #18240 moves MITM policy into the
PermissionProfile network tree.
## Why
1. After the hook model exists, the proxy needs a separate behavior
change that can be tested at the request path.
2. This PR makes hooked HTTPS hosts require MITM, evaluates inner
requests after CONNECT, mutates headers for matching hooks, and blocks
hooked hosts when no hook matches.
3. It also fixes the activation path so a permission profile with MITM
hook policy starts the managed proxy.
4. Keeping this separate from #18868 lets reviewers focus on runtime
effects, telemetry, and request mutation.
## Summary
1. Store compiled MITM hooks in network proxy state.
2. Require MITM for hooked hosts even when network mode is full.
3. Evaluate inner HTTPS requests against host specific hooks.
4. Apply hook actions by replacing request headers before forwarding.
5. Block hooked hosts when no hook matches and record block telemetry.
6. Treat profile MITM hook policy as managed proxy policy so the proxy
starts when needed.
7. Keep the duplicate authorization header replacement and query
preserving request rebuild in this runtime PR.
8. Add runtime tests and README guidance for hook enforcement.
## Validation
1. Ran the network proxy MITM policy tests.
2. Ran the hooked host CONNECT test.
3. Ran the authorization header replacement test.
4. Ran the core permission profile proxy activation test for MITM hooks.
5. Ran the scoped Clippy fixer for the network proxy crate.
6. Ran the scoped Clippy fixer for the core crate.
# Why
Compaction replaces the live conversation history, so hooks that use
`SessionStart` to re-inject durable model context need a way to run
again after that rewrite.
Related - #19905 adds dedicated compact lifecycle hooks
# What
- add `compact` as a supported `SessionStart` source and matcher value
- change pending `SessionStart` state from a single slot to a small FIFO
queue so `resume` / `startup` / `clear` can be preserved alongside a
later `compact`
- drain all queued `SessionStart` sources before the next model request,
preserving their original order
# Testing
The new integration coverage verifies both the basic `compact` matcher
path and the stacked `resume` -> `compact` case where both hooks
contribute `additionalContext` to the next model turn.
## Summary
Creates a personal-marketplace update flow for the plugin-creator skill
when iterating on an existing local plugin.
## Context
Plugin creation already had a scaffold path, but the follow-up story for
updating an existing local plugin during development was not explicit.
The goal of this change is to make that default personal-marketplace
update loop legible at the point of use instead of leaving it implied or
hidden behind a larger helper.
## Decision
Keep the scaffold flow intact, add a dedicated update/reinstall
reference centered on the personal marketplace, document the actual
`codex plugin add` and marketplace-check commands directly, and keep
helper automation narrowly scoped to the repetitive local-update steps.
## Changes
- update plugin-creator to point existing-plugin iteration at a
personal-marketplace update flow
- add `references/installing-and-updating.md` with the explicit
marketplace check and reinstall sequence
- add small helper scripts for reading marketplace names and updating
plugin versions during local iteration
## Tests
- `python3
codex-rs/skills/src/assets/samples/skill-creator/scripts/quick_validate.py
codex-rs/skills/src/assets/samples/plugin-creator`
- `python3 -m py_compile
codex-rs/skills/src/assets/samples/plugin-creator/scripts/create_basic_plugin.py
codex-rs/skills/src/assets/samples/plugin-creator/scripts/read_marketplace_name.py
codex-rs/skills/src/assets/samples/plugin-creator/scripts/update_plugin_cachebuster.py`
## Why
PR #20559 added opt-in strict config parsing to the config-loading
command surfaces, but `codex exec-server` was left out. That meant
`codex exec-server --strict-config` was rejected even though the command
can load config for remote registration, and local server startup had no
way to fail fast on misspelled config keys.
## What Changed
- Added `--strict-config` to `codex exec-server`.
- Allowed root-level inheritance from `codex --strict-config
exec-server`.
- Validated config before local exec-server startup when strict mode is
requested.
- Reused the loaded strict-config-aware config for remote exec-server
registration auth.
- Added CLI coverage showing `codex exec-server --strict-config` rejects
unknown config fields.
## Verification
- `cargo test -p codex-cli`
- New integration test:
`strict_config_rejects_unknown_config_fields_for_exec_server`
## Documentation
Any strict-config command list on developers.openai.com/codex should
include `codex exec-server` with the other supported config-loading
entry points.
## Stack
This is the foundation PR for the permission-profile inheritance stack.
- This PR adds config-level `extends` resolution and merge semantics.
- Follow-up: #23705 applies resolved profiles at runtime and updates the
active-profile protocol surfaces.
## Why
Permission profiles are starting to carry enough policy that
copy-pasting near-identical definitions becomes hard to review and easy
to drift. Before the runtime can consume inherited profiles, the config
layer needs one explicit resolver that can merge parent chains and
reject unsafe or invalid inheritance shapes.
## What changed
- Add `extends` to permission-profile TOML and resolve parent chains in
inheritance order.
- Merge inherited profile TOML with the existing config merge behavior
while preserving the permission-specific normalization needed for
network domain keys.
- Keep parent descriptions out of resolved child profiles and record
inherited profile names separately for downstream consumers.
- Reject undefined parents, unsupported built-in parents, and
inheritance cycles with targeted errors.
- Cover resolver behavior with TOML fixture tests and refresh the
generated config schema.
## Validation
- `cargo test -p codex-config`
- `cargo test -p codex-core permissions_profiles_`
## Stack
1. This PR adds MITM hook config and model only.
2. Runtime follow up: #20659 wires hook enforcement into the proxy
request path.
3. User facing config follow up: #18240 moves MITM policy into the
PermissionProfile network tree.
## Why
1. Viyat asked for the original parent PR to be split so reviewers can
inspect the policy model before request behavior changes.
2. This PR gives the proxy a typed MITM hook model, validation, matcher
compilation, permissions TOML plumbing, schema support, and config
tests.
3. This PR deliberately does not change CONNECT or MITM request
handling.
4. Keeping runtime behavior out of this PR makes the review boundary
simple: does the policy model parse, validate, compile, and lower
correctly.
## Summary
1. Add the MITM hook config model and matcher compilation.
2. Validate hosts, methods, paths, query matchers, header matchers,
secret sources, and reserved body matching.
3. Add wildcard matcher support for path, query value, and header value
matching.
4. Add permissions TOML and schema support for flat runtime hook config.
5. Add config loader tests for MITM hook overlay behavior.
## Validation
1. Regenerated the config schema.
2. Ran the network proxy MITM hook unit tests.
3. Ran the core permission profile MITM hook parsing tests.
4. Ran the core config schema fixture test.
5. Ran the scoped Clippy fixer for the network proxy crate.
6. Ran the scoped Clippy fixer for the core crate.
## Notes
1. Runtime enforcement moved to #20659.
2. User facing PermissionProfile TOML shape remains in #18240.
## Summary
Follow-up to #23636 review feedback: the Windows sandbox had two copies
of the same bundled-helper lookup order, one for
`codex-command-runner.exe` in `helper_materialization.rs` and one for
`codex-windows-sandbox-setup.exe` in `setup.rs`.
This PR centralizes that lookup in
`helper_materialization::bundled_executable_path_for_exe()` and has
setup reuse it for `codex-windows-sandbox-setup.exe`. The lookup
behavior is unchanged: direct sibling first, package-root
`codex-resources/` when running from `bin/`, then legacy sibling
`codex-resources/`.
## Test plan
- `cargo test -p codex-windows-sandbox`
## Notes
I also attempted `cargo check -p codex-windows-sandbox --target
x86_64-pc-windows-gnullvm`, but this local host is missing
`x86_64-w64-mingw32-clang`.
## Why
This is the next PR in the Windows sandbox migration stack after #22896.
The bottom PR introduces a Windows-local resolved permissions helper
while existing callers still start from legacy `SandboxPolicy`. This PR
moves the elevated runner IPC boundary to `PermissionProfile`, which
makes the direction of the stack visible without changing the public
core call sites yet.
Because that changes the CLI-to-command-runner message shape, the framed
IPC protocol version is bumped in the same PR so the boundary change is
explicit.
## What changed
- Replaced elevated IPC `policy_json_or_preset`/`sandbox_policy_cwd`
fields with `permission_profile`/`permission_profile_cwd`.
- Bumped the elevated command-runner IPC protocol to
`IPC_PROTOCOL_VERSION = 2` and switched parent/runner frames to use the
shared constant.
- Converted the parent elevated paths from the parsed legacy policy into
a materialized `PermissionProfile` before sending the runner request.
- Added `WindowsSandboxTokenMode` resolution for managed
`PermissionProfile` values and made the runner choose read-only vs
writable-root capability tokens from that resolved profile.
- Rejected disabled, external, unrestricted, and full-disk-write
profiles before token selection.
- Added IPC JSON coverage for tagged `PermissionProfile` payloads and
token-mode unit coverage for the resolved permission helper.
## Verification
- `cargo test -p codex-windows-sandbox`
- `just fix -p codex-windows-sandbox`
- `cargo check -p codex-windows-sandbox --target x86_64-pc-windows-msvc
--tests` was attempted locally but blocked before crate type-checking
because the macOS compiler environment lacks Windows C headers such as
`windows.h` and `assert.h`; GitHub Windows CI is the required
verification for the runner path.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22918).
* #23715
* #23714
* #23167
* #22923
* __->__ #22918
## Summary
DotSlash should resolve the same canonical package archives used by
standalone installers and npm platform packages, rather than continuing
to point at single-binary zstd artifacts or the older Linux bundle
archive.
This updates the Codex CLI and `codex-app-server` DotSlash release
config entries to match `codex-package-<target>.tar.gz` and
`codex-app-server-package-<target>.tar.gz`, with paths that select
`bin/codex` or `bin/codex-app-server` inside the extracted package. The
other helper outputs stay on their existing per-binary artifacts for
now.
## Test plan
- `python3 -m json.tool .github/dotslash-config.json > /dev/null`
- Ran a Python regex smoke test that checked every updated `codex` and
`codex-app-server` platform entry against the archive names emitted by
`.github/scripts/build-codex-package-archive.sh`.
## Why
Cloud-managed `requirements.toml` contents were deserialized without an
`AbsolutePathBuf` base directory. Relative managed
`permissions.filesystem.deny_read` glob entries therefore failed while
the equivalent local system requirements path succeeded under its
`AbsolutePathBufGuard`. This follows the `codex_home` base path
convention clarified in https://github.com/openai/codex/pull/15707.
## What changed
- Resolve cloud requirements TOML under an `AbsolutePathBufGuard` rooted
at `codex_home`.
- Reuse the same base for cloud requirements loaded from the signed
cache.
- Add a regression test for a relative cloud-managed `deny_read` glob.
## Validation
- `just fmt`
- `cargo test -p codex-cloud-requirements`
- `cargo clippy -p codex-cloud-requirements --all-targets --no-deps`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `git diff --check`
## Summary
The npm platform packages should stop carrying a bespoke native layout
now that the release workflow builds canonical Codex package archives.
Keeping npm on the same `bin/`, `codex-resources/`, and `codex-path/`
structure lets the Rust package-layout detection behave consistently
across standalone, npm, and future DotSlash installs.
This changes platform npm packages to stage the `codex-package` artifact
for each target under `vendor/<target>`. The Node launcher now resolves
`bin/codex` and prepends `codex-path`, while retaining legacy
`vendor/<target>/codex` and `vendor/<target>/path` fallback support for
local development and migration. The npm staging helper downloads
`codex-package` archives instead of rebuilding the CLI payload from
individual `codex`, `rg`, `bwrap`, and sandbox helper artifacts.
CI still needs to stage npm packages from historical rust-release
workflow artifacts that predate package archives, so the staging scripts
expose an explicit `--allow-legacy-codex-package` fallback. That
fallback synthesizes the canonical package layout from legacy per-binary
artifacts and is wired only into the CI smoke path; release staging
remains strict and continues to require real package archives.
For direct local use, `install_native_deps.py` now points its built-in
default workflow at the same recent artifact run used by CI and
automatically enables legacy package synthesis only when
`--workflow-url` is omitted. Explicit workflow URLs remain strict unless
callers opt in with `--allow-legacy-codex-package`.
## Test plan
- `python3 -m py_compile codex-cli/scripts/build_npm_package.py
codex-cli/scripts/install_native_deps.py scripts/stage_npm_packages.py
scripts/codex_package/cli.py`
- `node --check codex-cli/bin/codex.js`
- `ruby -e 'require "yaml";
YAML.load_file(".github/workflows/rust-release.yml");
YAML.load_file(".github/workflows/ci.yml"); puts "ok"'`
- Staged a synthetic `codex-linux-x64` platform package from a canonical
vendor tree and verified it copied only `bin/`, `codex-path/`,
`codex-resources/`, and `codex-package.json`.
- Imported `install_native_deps.py` and extracted a synthetic
`codex-package-x86_64-unknown-linux-musl.tar.gz` into `vendor/<target>`.
- Ran legacy-layout conversion smokes for Linux, Windows, and unsigned
macOS artifact naming.
- Ran a synthetic `install_native_deps.py` default-workflow smoke that
verifies legacy package synthesis is automatic only when
`--workflow-url` is omitted.
- `NPM_CONFIG_CACHE="$tmp_dir/npm-cache" python3
./scripts/stage_npm_packages.py --release-version 0.125.0 --workflow-url
https://github.com/openai/codex/actions/runs/26131514935 --package codex
--allow-legacy-codex-package --output-dir "$tmp_dir"`
- `node codex-cli/bin/codex.js --version`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23637).
* #23638
* __->__ #23637