## Why
The `dev/cc/ref-def` branch preserves richer JSON Schema detail for
connector tools, including `$defs` and nested shapes. That improves
fidelity, but it pushes the largest connector schemas well past the
intended tool-schema budget. This PR adds a best-effort compaction pass
for unusually large tool input schemas so the p99 and max tails stay
small while ordinary schemas are left alone.
## What Changed
- Added best-effort large-schema compaction in
`codex-rs/tools/src/json_schema.rs` after schema sanitization and
definition pruning.
- Compaction runs as a waterfall only while the compact JSON budget
proxy is exceeded:
1. Strip schema `description` metadata.
2. Drop root `$defs` / `definitions`.
3. Collapse deep nested complex schema objects to `{}`.
- Kept top-level argument names and immediate schema shape where
possible.
## Corpus Results
Scope: 2,025 schemas under `golden_schemas`, all parsed successfully.
Token count is `o200k_base` over compact JSON from
`parse_tool_input_schema`.
| Percentile | Before `origin/main` `4dbca61e20` | After branch
`dev/cc/ref-def` `f9bf071758` | After this PR |
|---|---:|---:|---:|
| p0 | 9 | 9 | 9 |
| p10 | 59 | 63 | 63 |
| p25 | 81 | 86 | 86 |
| p50 | 114 | 127 | 125 |
| p75 | 174 | 205 | 202 |
| p90 | 295 | 335 | 322 |
| p95 | 391 | 526 | 422 |
| p99 | 794 | 1,303 | 689 |
| max | 2,836 | 3,337 | 887 |
After this PR, `0 / 2,025` schemas are over 1k tokens.
### Compaction Savings
These are cumulative waterfall stages over the same corpus. Later passes
only run for schemas that are still over the compact JSON budget proxy.
| Stage | Total tokens | Step savings | Schemas changed by step |
|---|---:|---:|---:|
| No compaction | 391,862 | - | - |
| Strip schema `description` metadata | 350,961 | 40,901 | 66 |
| Drop root `$defs` / `definitions` | 340,683 | 10,278 | 13 |
| Collapse deep complex schemas to `{}` | 335,875 | 4,808 | 6 |
## Why
Extension tools that need conversation context should be able to read it
from the live tool invocation instead of reaching into thread
persistence themselves.
## What changed
- Add a `ConversationHistory` snapshot to extension `ToolCall`s and
populate it from the current raw in-memory response history.
- Expose all history items at this boundary so each extension can filter
and bound the subset it needs before consuming or forwarding it.
- Cover the adapter and registry dispatch paths and update existing
extension tests that construct `ToolCall` literals.
## Test plan
- `cargo test -p codex-tools`
- `cargo test -p codex-extension-api`
- `cargo test -p codex-goal-extension`
- `cargo test -p codex-memories-extension`
- `cargo test -p codex-core passes_turn_fields_to_extension_call`
- `cargo test -p codex-core
extension_tool_executors_are_model_visible_and_dispatchable`
# Why
Some connector tool input schemas use local JSON Schema references and
definition tables to avoid duplicating large nested shapes. Codex
previously lowered these schemas into the supported subset in a way that
could discard `$ref`-only schema objects and lose the corresponding
definitions, which made non-strict tool registration less faithful than
the original connector schema.
This keeps the existing minimal-lowering policy: Codex still does not
raw-pass through arbitrary JSON Schema, but it now preserves local
reference structure that fits the Responses-compatible subset and prunes
definition entries that cannot be reached by following `$ref`s from the
root schema after sanitization, including refs found transitively inside
other reachable definitions. The pruning matters because Responses
parses definition tables even when entries are unused, so keeping dead
definitions wastes prompt tokens.
# What changed
- Added `$ref`, `$defs`, and legacy `definitions` fields to the tool
`JsonSchema` representation.
- Updated `parse_tool_input_schema` lowering so `$ref`-only schema
objects survive sanitization instead of becoming `{}`.
- Sanitized definition tables recursively and dropped malformed
definition tables so non-strict registration degrades gracefully.
- Added reachability pruning for root definition tables by starting from
refs outside definition tables, then following refs inside reachable
definitions.
- Added JSON Pointer decoding for local definition refs such as
`#/$defs/Foo~1Bar`.
# Verification
ran local golden-schema probes against representative connector schemas
to validate behavior on real generated schemas:
| Golden schema | Before bytes | After bytes | `$defs` before -> after |
`$ref` before -> after | Result |
|---|---:|---:|---:|---:|---|
| `google_calendar/create_space` | 7111 | 4526 | 7 -> 7 | 7 -> 7 | all
definitions preserved because all are reachable |
| `figma/apply_file_variable_changes` | 4609 | 999 | 8 -> 5 | 8 -> 5 |
unused defs pruned after unsupported `oneOf` shapes lower away |
| `snowflake/list_catalog_integrations` | 1380 | 404 | 3 -> 0 | 0 -> 0 |
all defs pruned because none are referenced |
| `dropbox/create_shared_link` | 8894 | 1836 | 14 -> 4 | 9 -> 4 | only
defs reachable from the root schema after sanitization are retained,
including transitively through other retained defs |
Token increase across golden schema due to this change:
<img width="817" height="366" alt="Screenshot 2026-05-19 at 1 47 04 PM"
src="https://github.com/user-attachments/assets/d5c80fe9-da85-41e6-8ac7-a01d1e0b0f71"
/>
## Summary
The auto-review runtime sync path was assigning a raw
`PermissionProfile` into `runtime_permission_profile_override`, whose
field now expects `RuntimePermissionProfileOverride`. That broke the TUI
Bazel build.
This changes the assignment to store
`RuntimePermissionProfileOverride::from_config(&self.config)`, matching
the other runtime override paths and preserving the active profile and
network metadata with the permission profile.
## Summary
- Add us-gov-west-1 to the Bedrock Mantle supported region list
- Cover the GovCloud endpoint URL in the existing base_url unit test
## Test
- cargo test -p codex-model-provider
## Why
Experimental feature toggles and memory settings can update several
related config values in one interaction. Keeping those writes local in
a remote TUI session is especially dangerous because the UI can diverge
from the app-server config while also leaving behind partially stale
supporting keys.
This is **[3 of 4]** in a stacked series that moves TUI-owned config
mutations onto app-server APIs.
## What changed
- Routed feature flag persistence through app-server batch writes,
including the supporting reviewer and permission updates used by
guardian approval.
- Routed Windows sandbox mode persistence and legacy Windows feature
cleanup through app-server writes.
- Routed memory settings through app-server batch writes and updated the
TUI tests to exercise the embedded app-server path.
## Config keys affected
- `features.<feature_key>`
- `profiles.<profile>.features.<feature_key>`
- `approval_policy`
- `sandbox_mode`
- `approvals_reviewer`
- `windows.sandbox`
- `features.experimental_windows_sandbox`
- `features.elevated_windows_sandbox`
- `features.enable_experimental_windows_sandbox`
- Profile-scoped Windows legacy feature variants under
`profiles.<profile>.features.*`
- `memories.use_memories`
- `memories.generate_memories`
- Profile-scoped memory variants under `profiles.<profile>.memories.*`
## Suggested manual validation
- Connect the TUI to a remote app server, toggle guardian approval on
and off, and confirm the remote config updates
`features.guardian_approval`, reviewer state, approval policy, and
sandbox mode coherently.
- Toggle a default-false experimental feature at the root level, disable
it again, and confirm the key clears instead of lingering as an
unnecessary explicit `false`.
- Change memory settings and confirm the remote config updates both
memory keys while the running TUI reflects the new state.
- On Windows, switch sandbox mode through the TUI and confirm
`windows.sandbox` is updated while the legacy Windows feature keys are
cleared.
## Stack
1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
primary settings writes
2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
and skill enablement
3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
feature and memory toggles
4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
startup and onboarding bookkeeping
# What
When a normal hook fires inside a thread-spawned subagent, Codex now
includes these optional top-level fields in the hook input:
- `agent_id`: the child thread id
- `agent_type`: the subagent role
Root-agent hook inputs omit these fields. `SubagentStart` and
`SubagentStop` keep their existing required `agent_id` and `agent_type`
fields because those events are inherently subagent-scoped.
This does not change matcher behavior. Tool hooks still match on tool
name, compact hooks still match on trigger, and `UserPromptSubmit` still
ignores matchers. Only `SubagentStart` and `SubagentStop` match on
`agent_type`.
## Why
When remote control hits an auth failure such as a revoked or reused
refresh token, the websocket loop falls into reconnect backoff. If the
user fixes auth while that loop is sleeping, remote control can stay
offline until the old retry timer expires because nothing wakes the loop
or resets its exhausted auth recovery state.
## What Changed
Added an auth-change watch on `AuthManager` for refresh-relevant cached
auth updates.
The remote-control websocket loop now subscribes to that signal, resets
`UnauthorizedRecovery` and reconnect backoff when auth changes, and
retries immediately instead of waiting for the previous delay.
Updated the remote-control transport test to verify that reloading auth
with the now-available account id wakes enrollment before the prior
retry delay.
## Verification
`cargo test -p codex-app-server-transport
remote_control_waits_for_account_id_before_enrolling`
## Summary
- make rollout content search prefilter rollout files case-insensitively
- keep the no-ripgrep fallback scan and visible snippet matcher aligned
with that behavior
- cover a lowercase `thread/search` query matching mixed-case
conversation content
## Why
The rollout-backed `thread/search` path used exact string matching in
both its `rg` prefilter and semantic snippet generation. A content
result could be missed solely because the query casing did not match the
stored conversation text.
## Validation
- `just fmt`
- `cargo test -p codex-app-server thread_search_returns_content_matches`
- `cargo test -p codex-rollout`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `cargo build -p codex-cli`
- launched a local Electron dev instance with the rebuilt CLI binary
## Why
`rust-release` now publishes `codex-package-<target>.tar.gz` as the
canonical native package payload. npm staging should consume those
archives directly instead of keeping legacy synthesis code that fetched
`rg`, copied standalone binaries, and rebuilt an approximate package
layout.
That also means the package builder should not know the internal shape
of `codex-package`. It should extract and copy the target payload
wholesale so future layout changes stay localized to the archive
producer.
The release job stages `codex`, `codex-responses-api-proxy`, and
`codex-sdk` together, so native artifact download should be filtered,
observable, and shared across component installs. Since that native
hydration is now only used by release staging, keeping a separate
`install_native_deps.py` CLI adds an extra wrapper without a real
caller.
## What Changed
- Removed legacy `codex-package` synthesis and related compatibility
flags from npm staging.
- Folded the remaining native artifact hydration code into
`scripts/stage_npm_packages.py` and deleted
`codex-cli/scripts/install_native_deps.py`.
- Made platform package staging copy the full extracted target directory
instead of enumerating package entries.
- Kept non-`codex-package` native components under their component
directory name instead of using a legacy destination map.
- Split native staging by component set while sharing one
workflow-artifact cache across the invocation.
- Changed workflow artifact download to select target artifacts by name,
print sizes/progress, and reuse cached artifacts.
- Removed the implicit `CI=true` default from `build_npm_package.py`;
local CI-shaped runs should set that environment explicitly.
- Kept `npm pack` cache/log output in its temporary directory so packing
does not write to the user npm cache.
## Verification
- `python3 -m py_compile scripts/stage_npm_packages.py
codex-cli/scripts/build_npm_package.py`
- `python3 -m unittest discover -s scripts/codex_package -p "test_*.py"`
- `scripts/stage_npm_packages.py --help`
- `codex-cli/scripts/build_npm_package.py --help`
- Ran the release-shaped staging command from `rust-release.yml` against
workflow run https://github.com/openai/codex/actions/runs/26240748758
with `CI=true` set locally to match GitHub Actions:
```sh
CI=true python3 ./scripts/stage_npm_packages.py \
--release-version 0.133.0 \
--workflow-url https://github.com/openai/codex/actions/runs/26240748758 \
--package codex \
--package codex-responses-api-proxy \
--package codex-sdk
```
That completed successfully, downloaded only the six target artifacts
once, reused the cache for `codex-responses-api-proxy`, and produced all
nine npm tarballs. Generated tarballs and staging/artifact temp dirs
were cleaned afterward.
# Why
This is a follow-up stacked on top of the `plugin_hooks` default-on
change. Once we are comfortable making plugin hooks part of the normal
plugin behavior, the separate feature flag stops buying us much and
leaves extra branching/cache state behind.
# What
- remove the `PluginHooks` feature and generated config-schema entries
- make plugin hook loading/listing follow plugin enablement directly
- drop plugin-manager cache/state that only existed to distinguish
hook-flag toggles
- remove tests and fixtures that modeled `plugin_hooks = true/false`
## 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
## Why
`main` picked up two small Rust build failures after nearby merges:
- #23507 added a real handler for
`ServerNotification::ThreadSettingsUpdated`, but the same variant was
still listed in the ignored-notification match arm. Full Clippy runs
treat the resulting unreachable-pattern warning as an error.
- #23666 added `turn_id` and `truncation_policy` to
`codex_tools::ToolCall`, while the goal extension backend test fixtures
from the goal-extension work still used the old shape. That left
`codex-goal-extension` tests unable to compile once the branches met on
`main`.
## What changed
Removed the duplicate `ThreadSettingsUpdated` match pattern from
`tui/src/chatwidget/protocol.rs`.
Updated the goal extension test `tool_call` helper to populate the new
`ToolCall` fields, and reused that helper for the one direct literal
that still had the old field list.
## Verification
- `just fix -p codex-tui`
- `cargo test -p codex-goal-extension`
add standalone web search request types and a `codex-api` client ahead
of the extension-contributed search tool.
this adds typed commands/settings and opaque encrypted output handling
for the new standalone search flow. the endpoint types are close to
finalized but may still shift slightly as that API settles.
## What
- Preserve database accounting failures from the goal extension instead
of collapsing them into `None`
- Warn with turn/tool context when a flush fails
- Keep stop/abort accounting snapshots alive when the final flush did
not persist
## Why
PR #23696 can finish and discard a turn snapshot after
`account_thread_goal_usage` fails. That loses the final accumulated
accounting state silently. This follow-up keeps that failure explicit
and avoids deleting the local snapshot in the failing path.
## Testing
- `just fmt`
- `cargo test -p codex-goal-extension`
## Summary
Standalone installs should exercise the same canonical package archive
layout that release builds produce, rather than unpacking npm platform
packages and reconstructing a parallel install tree.
This updates `install.sh` and `install.ps1` to prefer
`codex-package-<target>.tar.gz` plus `codex-package_SHA256SUMS`
introduced in https://github.com/openai/codex/pull/23635, authenticate
the checksum manifest against GitHub release metadata, verify the
selected package archive against the authenticated manifest, and install
the package archive directly.
## Compatibility Notes
Package installs still leave a compatibility command at `current/codex`
for managed daemon flows, while visible command shims point at
`bin/codex` inside the package layout.
Recent releases that predate package archives still publish per-platform
npm artifacts, so both installers keep a legacy platform npm fallback
for those versions and verify those archives against release metadata
directly.
Releases old enough to publish only the single root
`codex-npm-<version>.tgz` archive are intentionally out of scope. The
installers fail clearly when neither package archives nor per-platform
npm archives are present.
On Windows, the runtime helper lookups now recognize package-layout
installs where `codex.exe` runs from `bin/`, so
`codex-command-runner.exe` and `codex-windows-sandbox-setup.exe` resolve
from the top-level `codex-resources/` directory. The direct-sibling and
older sibling-resource fallbacks are preserved.
## Test plan
- `sh -n scripts/install/install.sh`
- `bash -n scripts/install/install.sh`
- `pwsh -NoProfile -Command '$tokens=$null; $errors=$null; $null =
[System.Management.Automation.Language.Parser]::ParseFile("scripts/install/install.ps1",
[ref]$tokens, [ref]$errors); if ($errors.Count) { $errors | Format-List
*; exit 1 }'`
- `HOME="$home_dir" CODEX_HOME="$tmp_dir/codex-home"
CODEX_INSTALL_DIR="$bin_dir" PATH="$bin_dir:$PATH" sh
scripts/install/install.sh --release 0.125.0`
- Verified the 0.125.0 isolated install leaves the visible command
pointed at `current/codex` and includes the legacy `codex-resources/rg`
payload.
- `cargo test -p codex-windows-sandbox`
- `just fix -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/23636).
* #23638
* #23637
* __->__ #23636
## Why
Extension-owned tools currently receive a stripped `ToolCall` with only
`call_id`, `tool_name`, and `payload`.
That makes extension work that needs turn-local execution context
awkward, especially web-search extension work that needs the active
`truncation_policy` at tool invocation time.
Reconstructing that value from config or `ExtensionData` would be
indirect and could drift from the actual turn context, so the cleaner
fix is to pass the needed turn metadata directly on the extension-facing
invocation type.
## What changed
- added `turn_id` and `truncation_policy` to `codex_tools::ToolCall`
- populated those fields when core adapts `ToolInvocation` into an
extension tool call
- added a focused adapter test that verifies extension executors receive
the forwarded turn metadata
- updated the memories extension tests to construct the richer
`ToolCall`
- added the `codex-utils-output-truncation` dependency to `codex-tools`
and refreshed lockfiles
## Testing
- `cargo test -p codex-tools`
- `cargo test -p codex-memories-extension`
- `cargo test -p codex-core passes_turn_fields_to_extension_call`
- `just bazel-lock-update`
- `just bazel-lock-check`
Builds on #23502.
## Why
#23502 adds the app-server `thread/settings/update` API and matching
`thread/settings/updated` notification. The TUI already lets users
change thread-scoped settings such as model, reasoning effort, service
tier, approvals, permissions, personality, and collaboration mode, but
those updates need to flow through the app server so embedded and
connected clients observe the same thread state.
This is a rework (simplification) of PR
https://github.com/openai/codex/pull/22510. It has the same
functionality, but the underlying `thread/settings/update` api is now
simpler in that it no longer returns the effective settings as a
response. Now, clients receive the effective settings only through the
`thread/settings/updated` notification.
## What Changed
This updates the TUI to send `thread/settings/update` whenever those
thread-scoped settings change and to treat the RPC response as the
authoritative acknowledgement. It also routes `thread/settings/updated`
notifications back into cached session state and the visible chat widget
so active and inactive threads stay in sync after app-server-originated
changes.
The implementation is kept to the TUI layer: settings conversion and
merge logic live under `codex-rs/tui/src/app/thread_settings.rs`, with
dispatch/routing hooks in the existing app and chat widget paths.
## Verification
I manually tested using `codex app-server --listen unix://` and then
launching two copies of the TUI that use the same local app server. I
then resumed the same thread on both and verified that changes like plan
mode, fast mode, model, reasoning effort, etc. are reflected "live" in
the second client when modified in the first and vice versa.
## Why
App-server clients need a way to update a thread's next-turn settings
without starting a turn, adding transcript content, or waiting for turn
lifecycle events. This gives settings UI a direct path for durable
thread settings while clients observe the eventual effective state
through a notification.
This is a simplified rework of PR
https://github.com/openai/codex/pull/22509. In particular, it changes
the `thread/settings/update` api to return immediately rather than
waiting and returning the effective (updated) thread settings. This
makes the new api consistent with `turn/start` and greatly reduces the
complexity of the implementation relative to the earlier attempt.
## What Changed
- Adds experimental `thread/settings/update` with partial-update request
fields and an empty acknowledgment response.
- Adds experimental `thread/settings/updated`, carrying full effective
`ThreadSettings` and scoped by `threadId` to subscribed clients for the
affected thread.
- Shares durable settings validation with `turn/start`, including
`sandboxPolicy` plus `permissions` rejection and `serviceTier: null`
clearing.
- Emits the same settings notification when `turn/start` overrides
change the stored effective thread settings.
- Regenerates app-server protocol schema fixtures and updates
`app-server/README.md`.
## Why
The Windows sandbox migration away from the legacy `SandboxPolicy`
abstraction needs a small local bridge before IPC and core wiring can
move to `PermissionProfile`. Leaf helpers currently branch directly on
`WorkspaceWrite`, which spreads legacy assumptions through path planning
and token setup code.
This PR introduces a Windows-local resolved permissions view so those
helpers can ask Windows-specific questions about runtime
filesystem/network permissions without matching on the legacy policy
enum everywhere.
## What changed
- Added `ResolvedWindowsSandboxPermissions` in
`windows-sandbox-rs/src/resolved_permissions.rs`, with legacy
`SandboxPolicy` constructors for the current call sites.
- Moved `allow.rs` writable-root and read-only-subpath planning onto the
resolved permissions type.
- Preserved Windows `TEMP`/`TMP` writable-root behavior when the
effective policy includes writable tmpdir access.
- Avoided resolving Unix `:slash_tmp` or parent-process `TMPDIR` while
computing Windows writable roots.
- Reused the shared allow-path result for setup write-root gathering and
routed network-block selection through the resolved abstraction.
## Verification
- `cargo test -p codex-windows-sandbox`
- `just fix -p codex-windows-sandbox`
- GitHub CI restarted on the amended commit; Windows Bazel is the
required signal for the Windows-only code paths.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22896).
* #23715
* #23714
* #23167
* #22923
* #22918
* __->__ #22896
## Why
Pressing `Ctrl+C` or `Ctrl+D` in the TUI could make Codex pause during
shutdown when app-server background work still held outbound sender
clones.
Shutdown tracing against the current `~/.codex` path found three
relevant holders:
- `SkillsWatcher` kept its event-loop task alive until the shutdown
timeout path.
- `AppServerAttestationProvider` retained a strong
`Arc<OutgoingMessageSender>`, which could keep outbound teardown waiting
after the processor task had exited.
- A background `apps/list` task could still own an outbound sender when
shutdown began, causing the in-process app-server runtime to wait for
its outbound channel to close.
## What Changed
- Give `SkillsWatcher` an explicit shutdown `CancellationToken` and
cancel it from app-server teardown so its event loop drops the outbound
sender promptly.
- Change `AppServerAttestationProvider` to keep a
`Weak<OutgoingMessageSender>` and return immediately when it can no
longer be upgraded.
- Give `AppsRequestProcessor` a shutdown `CancellationToken` and cancel
in-flight background `apps/list` work during teardown.
## How to Test
1. Start Codex TUI from a real home configuration.
2. Press `Ctrl+C`.
3. Confirm Codex exits promptly instead of pausing during shutdown.
4. Repeat with `Ctrl+D` and confirm the same prompt exit path.
Focused manual trace validation from the investigation:
- Before the full fix, reproduced shutdown traces showed outbound
teardown waiting on lingering owners, including `attestation.provider=1`
and later `apps.list.task=1`.
- After the fix, fresh real-home `Ctrl+D` traces showed
`app_server.runtime.outbound_state_after_processor_join` with
`owners=none`, `app_server.runtime.wait_outbound_handle = 0ms`, and
total TUI app-server shutdown around `18ms`.
Targeted validation:
- `RUST_MIN_STACK=8388608 cargo test -p codex-app-server`
## Why
After the terminal-probe work in #23175, fresh-session startup still
waits for `thread/start` before the chat input can become usable. The
chat widget already has the machinery to hold early submissions until a
session is configured, so fresh `thread/start` does not need to stay on
the input-ready hot path.
Refs #16335.
## What
This PR starts fresh app-server threads in a background task, reports
completion through a startup app event, and attaches the primary session
once `thread/start` returns. Resume and fork startup paths remain
synchronous.
## Benchmark
In the local pty startup benchmark, this PR's pre-optimization base
branch, #23175, measured about 152ms median from launch to accepted chat
input. The stacked result measured about 66ms median, for an approximate
additional savings of 85-95ms. For broader context, the original `main`
baseline before either startup optimization was about 250.5ms median. We
also measured Codex 0.117.0 on the same machine at about 64.6ms median,
so the stacked branch is back in the old-startup-time range.
## Stack
1. [#23175: [1 of 2] Optimize TUI startup terminal
probes](https://github.com/openai/codex/pull/23175) — base PR
2. [#23176: [2 of 2] Start fresh TUI thread in
background](https://github.com/openai/codex/pull/23176) — this PR
## Verification
- `cargo test -p codex-tui`
## Why
The goal extension can create and surface goals, but the live
turn-accounting path still stopped short of persisting active-goal
progress. That leaves token and wall-clock usage, plus
`ThreadGoalUpdated` events, out of sync with the extension boundary once
work actually advances or a goal transitions out of active state.
## What changed
- Teach `GoalAccountingState` to track the current turn, active goal,
token deltas, and wall-clock progress snapshots against the persisted
goal id.
- Flush active-goal accounting from tool-finish, turn-stop, and
turn-abort lifecycle hooks, and emit `ThreadGoalUpdated` events when
persisted progress changes.
- Route `create_goal` and `update_goal` through the same accounting
state so new goals start from the right baseline, final progress is
flushed before status changes, and `update_goal` can mark a goal
`blocked` as well as `complete`.
- Keep budget-limited goals accruing through the end of the turn while
clearing local active-goal state once a turn or explicit update is
finished.
- Expand backend and lifecycle coverage around store ids, baseline
reset, tool-finish accounting, budget-limited carry-through, and
blocked-goal updates.
## Testing
- Added focused backend coverage in
`codex-rs/ext/goal/tests/goal_extension_backend.rs` for baseline reset,
tool-finish accounting, budget-limited turns, and blocked-goal updates.
- Extended `codex-rs/core/src/session/tests.rs` to assert that lifecycle
inputs expose the expected session, thread, and turn store ids.
## Summary
Standalone installers and other downstream package consumers need a
stable checksum source for the canonical package archives. Relying on
per-asset metadata makes that harder to consume uniformly, especially
when several package archives are produced in the same release.
This keeps the `codex-package-*.tar.gz` and
`codex-app-server-package-*.tar.gz` assets in the GitHub Release upload
set and adds `codex-package_SHA256SUMS` to `dist/` before the release is
created. The manifest contains one SHA-256 line per package archive and
fails the release job if no package archives are present.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23635).
* #23638
* #23637
* #23636
* __->__ #23635
## Summary
The Linux sandbox should find bundled `bwrap` through the same
package-layout abstraction as the rest of the runtime, instead of
maintaining a separate standalone-specific lookup path.
This adds an `InstallContext` helper for bundled resources and updates
`codex-linux-sandbox` to ask the current install context for
`codex-resources/bwrap` before falling back to the old
executable-relative probes. The tests cover npm-style, standalone, and
canonical package layouts so `bwrap` lookup follows the package
structure introduced earlier in the stack.
## Test plan
- `cargo test -p codex-install-context`
- `cargo test -p codex-linux-sandbox --lib`
- `just fix -p codex-install-context -p codex-linux-sandbox`
- `just bazel-lock-check`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23634).
* #23638
* #23637
* #23636
* #23635
* __->__ #23634
## Why
`code_mode_only_guides_all_tools_search_and_calls_deferred_app_tools`
was failing because code-mode prompt generation used the same nested
tool spec list for both the model-visible `exec` guide and the runtime
`ALL_TOOLS` surface. That allowed deferred MCP/app tools, such as
`calendar_timezone_option_99`, to leak into the `exec` description even
though they should only be discoverable through `ALL_TOOLS` at runtime.
## What changed
Split code-mode nested tool planning into two sets in
`core/src/tools/spec_plan.rs`:
- runtime nested tool specs still include deferred tools, so
`tools[...]` and `ALL_TOOLS` can call them
- `exec` prompt docs only render non-deferred tools, so deferred app
tools stay out of the model-visible guide
## Validation
- `cargo test -p codex-core --test all
code_mode_only_guides_all_tools_search_and_calls_deferred_app_tools --
--nocapture`
- looped the same focused test 5 additional times with `cargo test -q -p
codex-core --test all
code_mode_only_guides_all_tools_search_and_calls_deferred_app_tools`
## Why
The goal extension needs more context when a turn starts than
`turn_store` alone provides.
In particular, goal accounting needs the stable turn id, the effective
collaboration mode, and the cumulative token-usage baseline captured at
turn start so it can:
- suppress goal accounting for plan-mode turns
- compute exact per-turn deltas from cumulative `total_token_usage`
snapshots instead of relying on the most recent usage event alone
- keep the extension-owned accounting path aligned with the host turn
lifecycle
## What
- extend `codex_extension_api::TurnStartInput` to expose `turn_id`,
`collaboration_mode`, and `token_usage_at_turn_start`
- pass the full `TurnContext` plus the captured token-usage baseline
through the turn-start lifecycle emission path
- initialize goal turn accounting from the turn-start baseline and
collaboration mode
- switch goal token accounting to compute deltas from cumulative
`total_token_usage` snapshots
- add coverage for the new turn-start lifecycle fields and for
goal-accounting baseline behavior
## Testing
- added `turn_start_lifecycle_exposes_turn_metadata_and_token_baseline`
in `codex-rs/core/src/session/tests.rs`
- added `ext/goal/tests/accounting.rs` coverage for baseline-aware goal
accounting and plan-mode suppression
## Why
`ext/goal` already had the tool specs and contributor wiring for
`/goal`, but the installed tools still depended on a placeholder backend
that always errored. That meant the extension could not actually own
goal persistence even though the dedicated `thread_goals` store already
exists.
This change wires the extension tools directly to the dedicated goal
store so the extension can create, read, and complete goals against real
state instead of falling back to host-side placeholders.
## What changed
- make `install_with_backend(...)` require
`Arc<codex_state::StateRuntime>` so goal storage is always available
when the extension is installed
- remove the unused no-backend/public backend abstraction from
`ext/goal` and have the tool executors talk directly to `StateRuntime`
- map `thread_goals` rows into the existing protocol response shape for
`get_goal`, `create_goal`, and `update_goal`
- preserve current thread-list behavior by filling an empty thread
preview from the goal objective when a goal is created through the
extension path
- add integration coverage for the installed tool surface, including
successful goal creation and duplicate-create rejection
## Testing
- `cargo test -p codex-goal-extension`
## Why
Remote compaction currently sends a unary `POST /responses/compact` and
waits for the full response before replacing history or emitting the
completed `ContextCompaction` item. Unlike normal `/responses` streaming
requests, this unary compact request had no timeout boundary. If the
backend accepts the request and then stalls before returning a body, the
existing request retry policy never sees a transport error, so the
compact turn can remain stuck after the started item with no completion
or actionable error.
That matches the reported hang shape in issues such as #18363, where
logs show `responses/compact` was posted but no corresponding compact
completion followed. A bounded request timeout gives the existing retry
policy a concrete timeout error to retry instead of letting the user sit
indefinitely on automatic context compaction.
## What
- Add a request timeout to legacy `/responses/compact` calls.
- Size that timeout from the provider stream idle timeout with a
conservative multiplier, so the default compact attempt gets 20 minutes
rather than the 5 minute stream idle window.
- Map API transport timeouts to a request timeout error instead of the
child-process timeout message.
## Testing
- Not run (per request; CI will cover).
## Summary
- migrate exec-server remote registration naming from executor to
environment
- align CLI, public Rust exports, registry error messages, and relay
test fixtures with the environment registry contract
- keep the live registration path and response model consistent with
`/cloud/environment/{environment_id}/register`
## Verification
- `cargo test -p codex-exec-server
remote::tests::register_environment_posts_with_auth_provider_headers
--manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
- `cargo test -p codex-exec-server --test relay
multiplexed_remote_environment_routes_independent_virtual_streams
--manifest-path /Users/richardlee/code/codex/codex-rs/Cargo.toml`
- `cargo check -p codex-cli --manifest-path
/Users/richardlee/code/codex/codex-rs/Cargo.toml` (still running when PR
opened; will update after completion if needed)
add new `EncryptedContent` variant to `FunctionCallOutputContentItem`
ahead of standalone websearch.
we need to be able to receive and pass encrypted function call output
from the new web search endpoint back to responsesapi, as we cannot
expose direct search results.
## Why
The package-builder stack now creates a canonical Codex package
directory where the entrypoint lives under `bin/`, bundled helper
resources live under `codex-resources/`, and bundled PATH-style tools
live under `codex-path/`. That layout is not specific to the standalone
installer: npm, brew, install scripts, and manually unpacked artifacts
should all be able to use the same package shape.
The Rust runtime still only knew about the legacy standalone release
layout, where resources sit next to the executable. A packaged binary
therefore would not identify its package root or prefer the bundled `rg`
from `codex-path/`.
## What changed
- Adds `CodexPackageLayout` to `codex-install-context` and detects it
from an executable path shaped like `<package>/bin/<entrypoint>` when
`<package>/codex-package.json` is present.
- Splits `InstallContext` into an install `method` plus an optional
package layout so the layout is shared across npm, bun, brew,
standalone, and other launch contexts.
- Stores package-layout paths as `AbsolutePathBuf` values.
- Keeps `codex-resources/` and `codex-path/` optional so Codex can still
run with degraded behavior if sidecar directories are missing.
- Updates `InstallContext::rg_command()` to prefer bundled
`codex-path/rg` or `rg.exe`, then fall back to the legacy standalone
resources location, then system `rg`.
- Updates `codex doctor` reporting so package installs show package,
bin, resources, and path directories, and so bundled search detection
recognizes `codex-path/` for any install method.
## Test plan
- `cargo test -p codex-install-context`
- `cargo test -p codex-cli`
- `cargo test -p codex-tui
update_action::tests::maps_install_context_to_update_action`
- `just bazel-lock-check`
## Why
Release CI already builds the Codex entrypoints before staging
artifacts, and the package builder can now package those prebuilt
binaries directly. The workflow should produce package-shaped sidecar
archives from the same staged entrypoints that downstream distribution
channels will eventually consume, without rebuilding `codex` or
`codex-app-server` inside the packaging step.
This intentionally does **not** publish the new package archives as
GitHub Release assets yet. The archives are kept with workflow artifacts
until npm, Homebrew, `install.sh`, winget, and related consumers are
ready to switch over.
## What changed
- Adds a `Build Codex package archive` step to
`.github/workflows/rust-release.yml` after target artifacts are staged.
- Runs `scripts/build_codex_package.py` for both release bundles:
- `primary` builds `codex-package-${TARGET}.tar.gz` with `--variant
codex`.
- `app-server` builds `codex-app-server-package-${TARGET}.tar.gz` with
`--variant codex-app-server`.
- Passes `--entrypoint-bin target/${TARGET}/release/<entrypoint>` so
packages contain the entrypoint already built by the workflow.
- Deletes both package archive names before the final GitHub Release
upload so they remain workflow artifacts only for now.
## Verification
- Parsed `.github/workflows/rust-release.yml` with Ruby's YAML loader.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23582).
* #23596
* __->__ #23582
## Why
The package builder should describe the binaries it is actually
packaging, not require callers to restate release metadata out of band.
A caller-provided `--version` flag can drift from the workspace version,
but running the target entrypoint to discover its version breaks
cross-target packages when the produced binary cannot execute on the
build host.
This PR keeps package metadata tied to the repository source of truth by
reading `[workspace.package].version` from `codex-rs/Cargo.toml`. It
also prepares the package layout for `codex-app-server` packages: the
same package structure can now represent either the CLI entrypoint or
the app-server entrypoint while keeping shared sidecars such as `rg`,
`bwrap`, and Windows sandbox helpers in the existing package
directories.
## What changed
- Removes the `--version` CLI flag from
`scripts/build_codex_package.py`.
- Adds Cargo.toml version discovery for `codex-package.json.version` via
`codex-rs/Cargo.toml`.
- Adds `--entrypoint-bin` so callers can package a prebuilt entrypoint
instead of rebuilding it with Cargo.
- Makes `--variant` an explicit choice between `codex` and
`codex-app-server`, and uses it to select the cargo binary and packaged
`bin/` entrypoint name.
- Updates `scripts/codex_package/README.md` to document variants,
prebuilt entrypoints, and Cargo.toml version detection.
## Verification
- Compiled `scripts/build_codex_package.py` and
`scripts/codex_package/*.py` with `PYTHONDONTWRITEBYTECODE=1`.
- Ran `scripts/build_codex_package.py --help` and verified `--version`
is gone while `--variant` and `--entrypoint-bin` are present.
- Verified the package builder reads version `0.0.0` from
`codex-rs/Cargo.toml`.
- Built a fake cross-target `codex-app-server` package using a
non-executable `--entrypoint-bin`; verified metadata records version
`0.0.0`, variant `codex-app-server`, and `bin/codex-app-server` as the
entrypoint.
- Adds an explicit vertical marketplace kind for plugin/list that
fail-open fetches collection=vertical only when full remote plugins are
disabled.
- Renames the global remote marketplace/cache identity to
openai-curated-remote and materializes remote installs with backend
release versions and app manifests.
Fixes#23223.
## Why
Malformed AGENTS instructions should not fail silently. The reported
issue had invalid UTF-8 in a global `AGENTS.md`; before this change,
Codex treated that decode failure like a missing file, so the personal
instructions disappeared without a user-visible explanation and the
rollout had no `# AGENTS.md instructions` block.
Project-level AGENTS files already used lossy decoding, so their
instructions still appeared, but invalid bytes were replaced without
telling the user. Global and project AGENTS files should behave
consistently: keep usable instruction text when possible, and surface a
diagnostic when bytes had to be replaced.
## What changed
Global `AGENTS.override.md` and `AGENTS.md` loading now reads bytes and
decodes with replacement characters on invalid UTF-8, matching
project-level AGENTS behavior. Both global and project AGENTS loading
now emit a startup warning when invalid UTF-8 is found, and both keep
the instruction text with invalid byte sequences replaced.
Missing files, non-file candidates, empty files, and the existing
`AGENTS.override.md` before `AGENTS.md` precedence keep their current
behavior.
## How users see it
The warnings flow through the existing startup warning surface.
App-server clients receive config-time startup warnings as
`configWarning` notifications during initialization, and thread startup
emits startup warnings as thread-scoped `warning` notifications.
Global AGENTS invalid UTF-8 warnings can appear on both surfaces.
Project-level AGENTS invalid UTF-8 warnings are discovered while
building thread instructions, so they appear as thread-scoped `warning`
notifications. Clients that render warning notifications in the
conversation surface show the message as a visible diagnostic instead of
silently hiding or altering instructions.
## Why
Code mode can use nested unified exec calls as data sources. When those
calls omit `max_output_tokens`, code mode should receive raw command
output so the script can parse or summarize it itself. When code mode
does provide `max_output_tokens`, that explicit nested budget should be
respected, including values above the default unified exec limit, rather
than being capped before code mode sees the result.
## What
- Preserve direct unified exec truncation behavior, while letting
code-mode exec/write_stdin keep `max_output_tokens` as `None` unless
explicitly supplied.
- Make code-mode tool results use raw output when no explicit limit is
present, and use the explicit nested limit directly when one is
specified.
- Refactor unified exec output formatting so `truncated_output` takes
the caller-selected token budget.
- Add e2e integration coverage for explicit nested exec limits, omitted
nested exec limits, outer exec limit propagation, omitted-limit outputs
that exceed both the default and a small truncation policy, explicit
nested limits above those caps, and high explicit limits that still
compact larger command output.
- Reuse the code-mode turn setup helper while directly asserting the
exact exec output item in each test.
## Testing
- `just fmt`
- `git diff --check`
- Not run locally per repo guidance; CI should validate the e2e
integration tests.
## Why
Issue #23214 reports `/ps` showing no background terminals while the
status line still says it is waiting for a background terminal. The race
is in core: `write_stdin` can poll a process that exits before the
response returns. The process manager correctly returns `process_id:
None`, but the handler still emitted a `TerminalInteraction` event using
the requested session id, causing clients to believe a dead process was
still being polled.
Fixes#23214.
## What changed
- Suppress `TerminalInteraction` events for empty `write_stdin` polls
once `response.process_id` is `None`.
- Continue emitting interactions for non-empty stdin, even if that input
causes the process to exit before the response returns.
- Extend the unified exec integration test to assert completed empty
polls do not emit terminal interactions.
## Verification
- `cargo test -p codex-core --test all
unified_exec_emits_one_begin_and_one_end_event`
- `cargo test -p codex-core --test all
unified_exec_emits_terminal_interaction_for_write_stdin`
`cargo test -p codex-core` currently aborts in unrelated
`agent::control::tests::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
with a reproducible stack overflow.
## Why
Plugin and skill loading is useful as warmup and early validation, but
session startup does not need to wait for that work before it can
continue building the session. Keeping it on the serial startup path
adds avoidable latency to every fresh thread start.
We still want invalid skill configurations to show up quickly, and we
want the warmup to exercise the same plugin and skill manager caches
that the normal turn path uses.
## What changed
- moved plugin and skill warmup into the session startup async path
instead of eagerly awaiting it on the serial setup path
- kept the warmup using the session's resolved filesystem/environment
context so skill loading still sees the right roots
- preserved early skill-load error logging so broken skill
configurations still surface during startup
- left the per-turn plugin and skill loading path unchanged, so turns
still use the normal cached managers
## Testing
- Not run locally; relying on CI for validation.
## Why
Clients need a typed permission-profile catalog instead of
reconstructing that state from config internals.
## What changed
- Added `permissionProfile/list` to the app-server v2 protocol with
cursor pagination and optional `cwd`.
- The list response includes built-in permission profiles plus
config-defined `[permissions.<id>]` profiles from the effective config
for the request context.
- Permission profiles keep optional `description` metadata for display
purposes.
- App-server docs and schema fixtures are updated for the new RPC.
## Why
`codex-app-server` is published as a standalone release binary, so it
should support the same basic version inspection behavior users expect
from command-line tools. This is independent of package assembly:
package metadata now comes from `codex-rs/Cargo.toml`, but the
standalone app-server binary should still answer `--version` directly.
## What changed
- Enables Clap's generated `--version` flag for the `codex-app-server`
binary by adding `#[command(version)]` to its top-level parser.
## Verification
- Ran `cargo run -p codex-app-server --bin codex-app-server --
--version` and verified it prints `codex-app-server 0.0.0`.
## Why
`rust-ci-full` was paying the full Cargo nextest build-and-run cost once
per platform, with Windows ARM64 as the long pole. This change moves the
heavy work into one reusable per-platform flow: build a nextest archive
once, then replay it across four shards so the platform lane spends less
time running tests serially. For Windows ARM64, the archive is
cross-compiled on Windows x64 and replayed on native Windows ARM64
shards so the slow ARM64 machine is used for execution rather than
compilation.
## What changed
- split the `rust-ci-full` nextest matrix into five explicit
per-platform reusable-workflow calls
- add `.github/workflows/rust-ci-full-nextest-platform.yml` to build one
archive, upload timings/helpers, replay four nextest shards, upload
per-shard JUnit, and roll the shard status back up per platform
- add Windows CI helpers for Dev Drive setup and MSVC ARM64 linker
environment export so the Windows ARM64 archive can be produced on
Windows x64
- keep the existing Cargo git CLI fetch hardening inside the reusable
workflow, since caller workflow-level `env` does not flow through
`workflow_call`
- document the archive-backed shard shape in
`.github/workflows/README.md`
- raise the default nextest slow timeout to 30s so the sharded full-CI
path does not treat every >15s test as stuck
## Verification
- validated the archive/shard flow with live GitHub Actions runs on this
PR branch
- Windows ARM64 cross-compile latency on completed runs:
- https://github.com/openai/codex/actions/runs/26118759651: `34m30s`
lane e2e, `17m16s` archive build, `9m55s` shard phase
- https://github.com/openai/codex/actions/runs/26120777976: `30m36s`
lane e2e, `17m21s` archive build, `6m50s` shard phase
- comparable pre-cross-compile sharded Windows ARM64 runs were `55m01s`,
`50m21s`, and `46m42s`, so the completed cross-compile runs improved the
lane by roughly `12m` to `24m` versus the prior range
- latest corrected cross-compile run:
https://github.com/openai/codex/actions/runs/26120777976
- Windows ARM64 archive built successfully on Windows x64
- native Windows ARM64 shards started immediately after the archive
upload
- 3/4 Windows ARM64 shards passed; the failing shard hit the same
existing `code_mode` test failure seen outside this lane
- downloaded failed-shard JUnit XML from the validation runs and
confirmed the remaining red is from known test failures, not
archive/shard wiring
- no local Codex tests run per repo guidance
## Notes
- this PR does not change developers.openai.com documentation
## Why
The package builder should be easy to run during local iteration.
Requiring callers to provide both a target triple and an output
directory every time makes the common host-package case more awkward
than necessary.
This PR keeps explicit overrides available, but makes the default
invocation useful: build for the current host platform and place the
package in a fresh temporary directory. Because a temp output path is
otherwise easy to lose, the builder continues to print the final package
directory path when it completes.
## What changed
- Makes `--target` optional and maps the host OS/architecture to
supported Codex package target triples.
- Uses GNU Linux target triples for Linux host defaults, while keeping
the musl targets available for release jobs that pass `--target`
explicitly.
- Makes `--package-dir` optional and creates a new `codex-package-*`
temp directory when omitted.
- Documents the new defaults in `scripts/codex_package/README.md`.
## Verification
- Compiled `scripts/build_codex_package.py` and
`scripts/codex_package/*.py` with `PYTHONDONTWRITEBYTECODE=1`.
- Ran `scripts/build_codex_package.py --help` from outside the repo.
- Verified Linux host detection maps `x86_64` and `aarch64` to GNU
target triples.
- Ran a fake-Cargo package build while omitting both `--target` and
`--package-dir`; verified the generated metadata target, expected
package files, and printed temp package path.
- Ran a fake-Cargo package build for `x86_64-unknown-linux-gnu` and
verified `codex`, `bwrap`, and `rg` are assembled into the package.
## Why
`openai/codex#22169` added a regression test that expects an invalid
child `service_tier` to be rejected, but the test used
`Result::expect_err` on `SpawnAgentHandler::handle`. That requires the
`Ok` type to implement `Debug`, and this handler returns `Box<dyn
ToolOutput>`, so Bazel failed while compiling `codex-core` tests before
it could run them.
## What changed
- Capture the handler result and assert on `result.err()` instead of
calling `expect_err`.
- Keep the same `FunctionCallError::RespondToModel` assertion for the
rejected service tier.
## Verification
- `cargo test -p codex-core
spawn_agent_role_service_tier_does_not_hide_invalid_spawn_request`
## Summary
- remove the unreachable ARC monitor path from MCP tool approval
handling
- delete the unused ARC monitor module/tests and trim the orphaned
safety-monitor decision plumbing
- keep `always allow` approvals on the existing auto-approval
short-circuit without a dead monitor hop
## Testing
- `cargo test -p codex-core mcp_tool_call`
- `just fmt`
- `just fix -p codex-core`
- `git diff --check`
## Additional validation
- Attempted `cargo test -p codex-core`; the library test target passed,
then the integration target failed in this local environment.
- The narrower MCP-focused rerun passed its unit coverage and only hit
missing local `test_stdio_server` binaries in filtered integration
cases.
## Why
The Codex package builder should produce a complete package without
requiring callers to pre-populate `rg` under `codex-cli/vendor` or have
`dotslash` installed on `PATH`. The repo already tracks the
authoritative DotSlash manifest in `codex-cli/bin/rg`, so the builder
can read that metadata directly and fetch the correct ripgrep archive
for the target it is packaging.
## What changed
- Added `scripts/codex_package/ripgrep.py` to parse `codex-cli/bin/rg`
after stripping the shebang, select the target platform entry, download
the configured artifact, and verify the recorded size and SHA-256
digest.
- Added a cache under `$TMPDIR/codex-package/<target>-rg` so verified
archives can be reused without fetching again.
- Extracted `rg`/`rg.exe` from `tar.gz` and `zip` artifacts into the
package-builder cache, then copied that into `codex-path` through the
existing package layout flow.
- Kept `--rg-bin` as an explicit local override for offline tests and
unusual local workflows.
- Documented the default `rg` fetch/cache behavior in
`scripts/codex_package/README.md`.
## Verification
- Ran wrapper/module syntax compilation.
- Ran `scripts/build_codex_package.py --help` from `/private/tmp`.
- Ran a local manifest fetch test covering shebang-stripped manifest
parsing, `tar.gz` extraction, `zip` extraction, size/SHA-256
verification, and cache reuse after deleting the original source
archives.
- Ran fake-cargo package/archive builds for macOS, Linux, and Windows
target layouts with `--rg-bin`, including an assertion that generated
tar archives contain no duplicate member names.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23526).
* #23541
* __->__ #23526
This fixes a regression wher codex could start in the wrong directory
when a live local app-server socket was present. The issue was that
implicit local socket reuse was being treated like an explicit remote
workspace session, which dropped the invoking cwd unless --cd was
passed.
The change separates local socket transport from true remote workspace
semantics.
- Plain local startup keeps local cwd, trust, resume, picker, and
config-refresh behavior.
- Explicit --remote keeps the existing remote cwd behavior.
- Added coverage for launch target selection and local-session
filtering/cwd behavior.
Steps to test:
- Start a local app-server from a different directory than the repo you
want to use.
- Launch codex from a project/worktree without --cd.
- Confirm the session starts in the invoking directory, not the
app-server process directory.
- Confirm explicit codex --remote ... still preserves existing remote
behavior.
## Why
Custom agent roles are ordinary config layers, so a role file can
already express `service_tier` just like other config values. The
spawned-agent tier path needs to preserve that effective role config and
follow the same precedence pattern as model/reasoning.
## What changed
- Apply an explicit spawn-time `service_tier` onto the child config
before role application, so a role config layer can override it just
like role-defined model/reasoning settings do.
- Validate the final effective child tier after the final child model is
known, while still falling back to the parent tier when no child tier
survives.
- Add focused integration coverage for both v1 and v2 proving role TOML
loads a service tier, spawned children keep that role-configured tier,
and a role tier wins over a conflicting spawn-time tier.
## Validation
- `just fmt`
- `git diff --check`
- Local Rust tests not run, per repo guidance; CI should exercise the
new coverage.
# Summary
Unix-socket app-server startup can currently race when multiple launch
attempts target the same `CODEX_HOME`. Those processes can overlap
before the control socket exists, which lets them enter SQLite state
initialization concurrently and reproduce the startup corruption pattern
seen in SSH mode.
This change makes the app-server own that singleton startup guarantee.
Unix-socket startup now takes a `CODEX_HOME`-scoped advisory lock before
SQLite initialization, runs the existing control-socket preparation
check while holding that lock, returns the established `AddrInUse` error
when another live listener already owns the socket, and releases the
lock once the new listener has bound its socket.
# Design decisions
- The singleton rule lives in `app-server --listen unix://`, not in a
desktop-only caller path, so every Unix-socket launch gets the same race
protection.
- A duplicate raw app-server launch returns an error instead of silently
succeeding. The attach operation remains `app-server proxy`, which
continues to connect to an already-running listener.
- The lock is held only across the dangerous startup window: socket
preparation, SQLite initialization, and socket bind. It is not held for
the app-server lifetime.
- Listener detection stays in `prepare_control_socket_path(...)`, so the
preexisting live-listener and stale-socket behavior remains the single
source of truth.
# Testing
Tests: targeted Unix-socket transport tests on the branch checkout, full
`codex-cli` build on `efrazer-db10`, and an SSH-style smoke on
`efrazer-db10` covering concurrent app-server starts, explicit
duplicate-start errors, and absence of SQLite startup-error matches in
launch logs.
## Summary
- Add `list_available_plugins_to_install` as the inventory step for
plugin and connector install suggestions.
- Slim `request_plugin_install` so it only handles the actual
elicitation, instead of carrying the full discoverable list in its
prompt.
- Emit send-time telemetry when an install elicitation is dispatched,
including requested tool identity in the event payload.
- Emit install-result telemetry through `SessionTelemetry`, including
tool type, user response action, and completion status.
- Update registration and tests to cover the new two-step flow while
keeping the existing `tool_suggest` feature gate unchanged.
## Testing
- `just fmt`
- `cargo test -p codex-tools`
- `cargo test -p codex-core request_plugin_install`
- `cargo test -p codex-core list_available_plugins_to_install`
- `cargo test -p codex-core
install_suggestion_tools_can_be_registered_without_search_tool`
- `cargo test -p codex-otel
manager_records_plugin_install_suggestion_metric`
- `cargo test -p codex-otel
manager_records_plugin_install_elicitation_sent_metric`
- `just fix -p codex-core`
- `just fix -p codex-tools`
- `just fix -p codex-otel`
- `cargo check -p codex-core`
## Summary
- move local-only app-server gating out of `MessageProcessor`
- let `fs/*`, `command/exec`, and `process/spawn` resolve local
availability inside their owning processors
- keep `fs/*` mounted for the future environment-param path while
preserving current no-local error behavior
## Validation
- not run locally per Codex repo guidance
## Summary
- Coerce `path: ""` to `None` at the v2 protocol params deserialization
boundary for `thread/resume` and `thread/fork`.
- Restore the pre-ThreadStore running-thread resume behavior: if
`threadId` is already running, rejoin it by id and treat a non-empty
`path` only as a consistency check; otherwise cold resume keeps `history
> path > threadId` precedence.
- Add protocol, resume, and fork regression coverage for empty path
payloads; refresh app-server schema fixtures for the clarified params
docs.
## Tests
- `just fmt`
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol
thread_path_params_deserialize_empty_path_as_none`
- `cargo test -p codex-app-server-protocol --test schema_fixtures`
- `cargo test -p codex-app-server empty_path`
- `RUST_MIN_STACK=8388608 cargo test -p codex-app-server --test all
thread_resume_rejects_mismatched_path_for_running_thread_id`
- `RUST_MIN_STACK=8388608 cargo test -p codex-app-server --test all
thread_resume_uses_path_over_non_running_thread_id`
## Why
Plan mode questionnaires reuse the shared composer for free-form
answers, but the surrounding `request_user_input` overlay still treated
every `KeyCode::Enter` as “advance to the next question.” That made
`Shift+Enter` insert a newline in the composer and then immediately
advance the questionnaire anyway.
Fixes#23448.
## What Changed
- pass the live `RuntimeKeymap` into `RequestUserInputOverlay` so its
embedded composer honors existing `/keymap` composer/editor remaps
- advance free-form questions only on the configured composer submit
binding, instead of any Enter-shaped key event
- add regressions for `Shift+Enter` newline behavior and configured
composer submit bindings inside the questionnaire UI
## How to Test
1. Start Codex in Plan mode and trigger a `request_user_input`
questionnaire with a free-form answer field.
2. Focus the free-form field, type a line, then press `Shift+Enter`.
3. Confirm the answer gains a newline and the questionnaire stays on the
same question.
4. Press the configured submit binding, or plain `Enter` with the
default keymap, and confirm the questionnaire advances as before.
Targeted tests:
- `cargo test -p codex-tui
bottom_pane::request_user_input::tests::freeform_ -- --nocapture`
## Notes
- `cargo test -p codex-tui` still reaches an unrelated existing stack
overflow in
`app::tests::discard_side_thread_removes_agent_navigation_entry` on this
checkout.
- `just argument-comment-lint` is locally blocked by Bazel analysis
failing in external `compiler-rt` before the lint runs.
## Why
Exec-server websocket handling had separate reader and writer tasks for
the same socket. That made websocket control-frame handling asymmetric:
the task reading frames could observe `Ping`, but the task allowed to
write frames was elsewhere. This PR moves each physical websocket onto
one always-running pump so the socket owner can handle application
frames and websocket control frames together.
## What changed
- Refactored direct exec-server websocket connections in `connection.rs`
to use one task that owns the websocket for outbound JSON-RPC, inbound
JSON-RPC, periodic keepalive pings, and `Ping` -> `Pong` replies.
- Refactored relay websocket handling in `relay.rs` the same way for
both the harness-side logical connection and the multiplexed executor
physical socket.
- Preserved the existing keepalive ownership policy: outbound direct
websocket clients still send periodic pings, inbound Axum accepts only
reply with pongs, and relay physical websocket endpoints keep their
existing periodic pings.
- Added focused websocket pump tests for ping/pong, binary JSON-RPC,
relay data, malformed relay text frames, and close/disconnect behavior.
- Reconnect behavior is intentionally left for a follow-up.
## Validation
- Devbox Bazel focused unit target:
- `//codex-rs/exec-server:exec-server-unit-tests
--test_filter='websocket_connection_|harness_connection_|multiplexed_executor_'`
## Why
Codex CLI packaging is currently split across npm staging, standalone
installers, and release bundle creation, which makes it hard to define
and validate a single valid package directory. This adds the first
standalone package builder so later release paths can converge on the
same canonical layout.
## What changed
- Added `scripts/build_codex_package.py` as the stable executable
wrapper around `scripts/codex_package`.
- Added modules for CLI parsing, target metadata, grouped cargo builds,
package layout validation, and archive writing.
- The builder creates a package directory with `codex-package.json`,
`bin/`, `codex-resources/`, and `codex-path`, and can serialize it as
`.tar.gz`, `.tar.zst`, or `.zip`.
- Source-built artifacts are built by one grouped `cargo build`: `codex`
for all targets, `bwrap` for Linux, and the Windows sandbox helpers for
Windows. `rg` remains an input because it is vendored from upstream
rather than built from this repo.
- Added `scripts/codex_package/README.md` to document the package
layout, source-built artifacts, and cargo profile behavior.
## Verification
- Ran wrapper/module syntax compilation.
- Ran `scripts/build_codex_package.py --help` from `/private/tmp`.
- Ran fake-cargo package/archive builds for macOS, Linux, and Windows
target layouts, including an assertion that generated tar archives
contain no duplicate member names.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23513).
* #23526
* __->__ #23513
# What
`SubagentStart` runs once when Codex creates a thread-spawned subagent,
before that child sends its first model request. Thread-spawned
subagents use `SubagentStart` instead of the normal root-agent
`SessionStart` hook.
Configured handlers match on the subagent `agent_type`, using the same
value passed to `spawn_agent`. When no agent type is specified, Codex
uses the default agent type.
Hook input includes the normal session-start fields plus:
- `agent_id`: the child thread id.
- `agent_type`: the resolved subagent type.
`SubagentStart` may return `hookSpecificOutput.additionalContext`. That
context is added to the child conversation before the first model
request.
# Lifecycle Scope
Only thread-spawned subagents run `SubagentStart`.
Internal/system subagents such as Review, Compact, MemoryConsolidation,
and Other do not run normal `SessionStart` hooks and do not run
`SubagentStart`. This avoids exposing synthetic matcher labels for
internal implementation paths.
Also the `SessionStart` hook no longer fires for subagents, this matches
behavior with other coding agents' implementation
# Stack
1. This PR: add `SubagentStart`.
2. #22873: add `SubagentStop`.
3. #22882: add subagent identity to normal hook inputs.
## Context
The CLI rate-limit surfaces previously described usage windows as fixed
5-hour and weekly limits. We want the CLI to display whatever supported
rate-limit period the server returns instead of assuming a 5-hour/1-week
pair. This supports generalized Codex rate-limit periods.
## Summary
- Formats CLI rate-limit warning/status labels only for the supported
returned window durations: approximate 5h, daily, weekly, monthly, and
annual.
- Uses generic fallback copy when a primary or secondary window has no
duration, so missing secondary protection data does not produce stale
weekly copy.
- Uses generic fallback copy for unsupported window durations instead of
adding arbitrary hourly, multi-day, multi-week, or multi-year labels.
- Updates status line and terminal title setup descriptions/previews to
talk about primary/secondary usage limits rather than fixed 5h/weekly
limits.
- Adds rendered insta snapshot coverage for the updated rate-limit
status surfaces and `/status` fallback labels.
## Tests
Tested locally:
- one primary window
- one secondary window
- primary and secondary window
## Why
Filesystem permission profiles used `none` for deny-read entries, which
is less direct than the action the entry actually represents. This
change makes `deny` the canonical filesystem permission spelling while
preserving compatibility for older configs that still send `none`.
## What changed
- rename `FileSystemAccessMode::None` to `Deny`
- serialize and generate schemas with `deny` as the canonical value
- retain `none` only as a legacy input alias for temporary config
compatibility
- update filesystem glob diagnostics and regression coverage to use the
canonical spelling
- refresh config and app-server schema fixtures to match the new wire
shape
## Validation
- `cargo test -p codex-protocol`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core config_toml_deserializes_permission_profiles
--lib`
- `cargo test -p codex-core
read_write_glob_patterns_still_reject_non_subpath_globs --lib`
Earlier in the session, a broad `cargo test -p codex-core` run reached
unrelated pre-existing failures in timing/snapshot/git-info tests under
this environment; the targeted surfaces touched by this PR passed
cleanly.
## Why
The v1 sub-agent tools are a single tool family, but they were exposed
as separate flat function tools. This makes the model-visible surface
less clearly grouped and leaves the legacy names in the same flat
namespace as newer agent tooling.
## What
- Wraps the v1 `spawn_agent`, `send_input`, `resume_agent`,
`wait_agent`, and `close_agent` specs in the `multi_agent_v1` namespace.
- Registers the corresponding handlers with namespaced runtime tool
names.
- Updates tool-planning, deferred tool search, and sub-agent
notification tests to assert the namespace shape and child `spawn_agent`
lookup.
## Verification
- Updated `codex-core` coverage for the v1 multi-agent tool plan,
deferred tool search output, and sub-agent tool descriptions.
## Why
`ContextualUserFragment` needs to be usable behind `dyn` for render-only
paths, but associated constants made the trait non-object-safe.
## What changed
- Replaced associated constants with trait methods so `dyn
ContextualUserFragment` can render fragments.
- Preserved the existing typed `T::matches_text(text)` registration
pattern via `type_markers()`.
- Kept default `render()` on the main trait so implementations only
provide role, markers, and body.
- Added unit coverage for rendering a `Box<dyn ContextualUserFragment>`.
## Verification
- `cargo test -p codex-core contextual_user_fragment_is_dyn_compatible`
- `just fix -p codex-core`
## Why
App and skill toggles are user config mutations too. When the TUI is
attached to a remote app server, writing those toggles into the local
`config.toml` makes the UI report success without updating the server
that actually owns the session.
This is **[2 of 4]** in a stacked series that moves TUI-owned config
mutations onto app-server APIs.
## What changed
- Routed app enable/disable persistence through app-server config batch
writes.
- Routed skill enable/disable persistence through `skills/config/write`.
- Avoided refreshing local config from disk after these writes when the
TUI is connected to a remote app server.
## Config keys affected
- `apps.<app_id>.enabled`
- `apps.<app_id>.disabled_reason`
- `[[skills.config]]` entries keyed by `path`, with `enabled = false`
used for persisted disables
## Suggested manual validation
- Connect the TUI to a remote app server, disable an app, reconnect, and
confirm the app remains disabled from remote config rather than local
disk state.
- Re-enable the same app and confirm both `apps.<app_id>.enabled` and
`apps.<app_id>.disabled_reason` are cleared remotely.
- Disable a skill in the manage-skills UI and confirm a remote
`[[skills.config]]` disable entry appears.
- Re-enable that skill and confirm the disable entry is removed and the
effective enabled state updates without relying on local config reloads.
## Stack
1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
primary settings writes
2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
and skill enablement
3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
feature and memory toggles
4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
startup and onboarding bookkeeping
## Why
Steered input was queued as a `ResponseInputItem`, then parsed back into
a user message before recording. That path loses information that only
exists on `UserInput`, such as UI text elements.
This change keeps turn-local pending input typed as either original
`UserInput` or existing response items, so steered user input reaches
user-message recording without being reconstructed from a response item.
## What changed
- Add `TurnInput` for active-turn pending input.
- Queue `Session::steer_input` as `TurnInput::UserInput`.
- Run pending-input hook inspection only for `TurnInput::UserInput`.
- Process drained pending input item by item: accepted items are
recorded, blocked items append hook context and are skipped.
- Remove the pending-input prepend/requeue path.
## Validation
- `just fmt`
- `just fix -p codex-core`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core --lib
session::tests::task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input
-- --nocapture`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core --lib steer_input`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core --lib pending_input`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core --test all
pending_input`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core` (unit tests passed:
1835 passed, 0 failed, 4 ignored; integration `all` target failed due
missing helper binaries such as `codex`/`test_stdio_server` plus
unrelated MCP/search/code-mode expectations)
## Why
`run_turn` was still hand-building hook payloads and lifecycle events
for a couple of hook paths. Most hook call sites already delegate
request construction and event emission to `hook_runtime`, which keeps
turn orchestration focused on model-flow decisions rather than hook
plumbing.
This also keeps the legacy `after_agent` message extraction next to the
legacy hook dispatch instead of leaving response-item walking in
`run_turn`.
## What changed
- Added `run_stop_hooks` in `hook_runtime` to build `StopRequest`, emit
preview start events, run the hook, and emit completion events.
- Added `run_legacy_after_agent_hook` in `hook_runtime` to build and
dispatch the legacy `AfterAgent` hook payload, including extracting
input messages from response items.
- Updated `run_turn` to call the hook runtime helpers and keep only the
resulting continuation/block/stop decisions inline.
- Removed the repeated pending session-start hook check from the run
loop.
## Validation
- `cargo test -p codex-core hook_runtime`
## Why
`turn/start` already accepts an input array on the wire, including an
empty array, but core treated empty input as a no-op before the turn
could reach the model. App-server clients need to be able to start a
real turn even when there is no new user message, for example to let the
model proceed from existing thread context.
## What changed
- Removed the `run_turn` early return that skipped empty-input turns
when there was no pending input.
- Kept empty active-turn steering rejected by moving the `steer_input`
empty-input check until after core has determined whether there is an
active regular turn.
- Empty regular turns now refresh `previous_turn_settings` like other
regular turns, so follow-up context injection state advances
consistently.
- Added an app-server v2 integration test proving `turn/start` with
`input: []` emits started/completed notifications, sends one Responses
request, and does not synthesize an empty user message.
## Validation
- `cargo test -p codex-app-server --test all
turn_start_with_empty_input_runs_model_request`
Summary: defer v1 multi-agent tools when tool_search and namespace tools
are available; keep concise searchable descriptions and move the v1
usage guidance into developer instructions; add targeted coverage.
Testing: not run per request; ran just fmt.
## Why
`model_auto_compact_token_limit` has only been able to budget the full
active context. That makes it hard to set a small "growth since
compaction" budget for sessions that preserve a large carried window
prefix: the preserved prefix can consume the whole budget and force
immediate repeated compaction.
This PR adds an opt-in `body_after_prefix` scope so callers can apply
`model_auto_compact_token_limit` to sampled output and later growth
after the current carried prefix, while still forcing compaction before
the full model context window is exhausted.
## What changed
- Adds `AutoCompactTokenLimitScope` with the existing `total` behavior
as the default and a new `body_after_prefix` mode:
[`config_types.rs`](973806b1cb/codex-rs/protocol/src/config_types.rs (L24-L37)).
- Threads `model_auto_compact_token_limit_scope` through config loading,
`Config`, `core-api`, and app-server v2 schema/TypeScript generation.
- Records the first observed input-token count for a `body_after_prefix`
compaction window and uses it as the baseline when deciding whether the
scoped auto-compaction budget is exhausted:
[`turn.rs`](973806b1cb/codex-rs/core/src/session/turn.rs (L743-L781)).
- Keeps a hard context-window cap in `body_after_prefix`, so scoped
budgeting cannot let the active context overrun the usable window.
## Verification
Added compact-suite coverage for the two key behaviors:
`body_after_prefix` does not re-compact just because the carried prefix
is larger than the scoped budget, and it still compacts when the total
active context reaches the configured context window:
[`compact.rs`](973806b1cb/codex-rs/core/tests/suite/compact.rs (L3003-L3128)).
## Why
`codex-tools` is meant to hold reusable tool primitives, but
`ToolsConfig` had become a second copy of core runtime decisions instead
of a small shared contract. It carried provider capabilities, auth/model
gates, permission and environment state, web/search/image feature gates,
multi-agent settings, and goal availability from core into `codex-tools`
([definition](22dd9ad392/codex-rs/tools/src/tool_config.rs (L97)),
[stored on each
`TurnContext`](22dd9ad392/codex-rs/core/src/session/turn_context.rs (L87))).
Every session/context variant then had to build and mutate that snapshot
before assembling tools.
This PR removes that master object instead of renaming it. Tool planning
now reads the live `TurnContext`, where `codex-core` already owns those
decisions, while `codex-tools` keeps only reusable primitives and a
generic `ToolSetBuilder`/`ToolSet` accumulator.
## What Changed
- Removed `ToolsConfig` / `ToolsConfigParams` from `codex-tools`; the
crate keeps the shared helpers that still belong there, including
request-user-input mode selection, shell backend/type resolution,
`UnifiedExecShellMode`, and `ToolEnvironmentMode`.
- Replaced config-snapshot planning with `ToolRouter::from_turn_context`
and a `spec_plan` pipeline over `CoreToolPlanContext`, deriving provider
capabilities, auth gates, model support, feature gates, environment
count, goal support, multi-agent options, web search, and image
generation from the authoritative turn state.
- Added generic `codex_tools::ToolSetBuilder` / `ToolSet`, plus the
small core adapter needed to accumulate `CoreToolRuntime` values and
hosted model specs.
- Added the `tool_family::shell` registration module and moved
shell/unified-exec/memory accounting call sites to read the narrow
per-turn fields directly.
- Narrowed `TurnContext` to the remaining explicit per-turn fields
needed by planning: `available_models`, `unified_exec_shell_mode`, and
`goal_tools_supported`.
- Reworked MCP exposure and tool-search setup so deferred/direct MCP
behavior is driven by the current turn rather than a precomputed config
snapshot.
- Replaced the large expected-spec fixture tests with focused
behavior-level coverage for shell tools, environments, goal and
agent-job gates, MCP direct/deferred exposure, tool search,
request-plugin-install, code mode, multi-agent mode, hosted tools, and
extension executor dispatch.
## Verification
- `cargo check -p codex-tools`
- `cargo check -p codex-core --lib`
- `cargo test -p codex-tools`
- `cargo test -p codex-core spec_plan --lib`
- `cargo test -p codex-core router --lib`
## Why
Thread goals are moving toward extension-owned runtime behavior, but
their persisted state was still stored in the shared state database.
This makes the goal store harder to isolate and keeps future storage
splits tied to ad hoc runtime plumbing.
This PR gives goals their own SQLite database while keeping the existing
`StateRuntime` entry point. The goal is to make this the pattern for
adding more dedicated runtime databases later.
This also reduce load on existing DB and reduce contention
## Limitation
Thread preview from goal is not supported anymore. I'm looking into this
[EDIT]: solved
## What changed
- Added a dedicated `goals_1.sqlite` database with its own
`goals_migrations` directory.
- Moved `thread_goals` creation into the goals DB migration set.
- Dropped the old `thread_goals` table from the main state DB with a
normal state migration. There is intentionally no backfill for existing
goal rows.
- Changed `GoalStore` to be backed only by the goals DB pool.
- Removed the old goal-write side effect that filled empty
`threads.preview` values from the goal objective.
- Added shared runtime DB path metadata so startup, telemetry, `codex
doctor`, and repair handling can include future DBs without bespoke path
lists.
- Updated Bazel compile data so the new goals migration directory is
available to `sqlx::migrate!`.
## Verification
- `cargo check --tests -p codex-state -p codex-cli -p codex-core -p
codex-app-server`
- `just fix -p codex-state`
- `just fix -p codex-cli`
- `just fix -p codex-app-server`
## Why
Full-history agent forks should continue from the same prompt prefix as
the parent. Dropping the stored `TurnContext` baseline forced the child
to rebuild startup context on its first turn, which can duplicate
developer instructions and also loses the cache continuity that a
full-history fork is supposed to preserve.
Truncated forks are different: once we keep only the last N turns, the
original prompt prefix is no longer intact, so the child must establish
a fresh context baseline.
## What changed
- Preserve `RolloutItem::TurnContext` when forking with
`SpawnAgentForkMode::FullHistory`, and keep dropping it for truncated
forks:
4090717d94/codex-rs/core/src/agent/control.rs (L98-L126)
and
4090717d94/codex-rs/core/src/agent/control.rs (L399-L401)
- Remove the special-case MultiAgentV2 usage-hint filtering path.
Full-history fork now preserves the cached developer prefix instead of
trying to reconstruct part of it.
- Extend the fork coverage to assert both sides of the contract:
full-history forks keep the parent reference baseline, while last-N
forks rebuild context after truncation:
4090717d94/codex-rs/core/src/agent/control_tests.rs (L603-L759)
and
4090717d94/codex-rs/core/src/agent/control_tests.rs (L854-L977)
## Verification
- `cargo test -p codex-core
spawn_agent_can_fork_parent_thread_history_with_sanitized_items --
--nocapture`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core
spawn_agent_fork_last_n_turns_keeps_only_recent_turns -- --nocapture`
## Why
The `/permissions` picker needs a config-level way to distinguish legacy
anonymous presets from named permission-profile mode. That signal cannot
be inferred reliably in the TUI, especially for the edge case where
`default_permissions = ":workspace"` is present without a
`[permissions]` table.
## What changed
- Expose whether the merged config is explicitly in permission-profile
mode.
- Expose the configured custom permission profile IDs alongside the
built-in profile semantics.
- Add regression coverage for profile mode detection and custom profile
metadata, including the `default_permissions = ":workspace"` case.
- Update the thread-manager sample config literal to match the expanded
config shape.
## Stack
1. **This PR**: config metadata needed by downstream permission-profile
consumers.
2. [#22931](https://github.com/openai/codex/pull/22931): refresh active
permission profiles through runtime/session/network state.
3. [#21559](https://github.com/openai/codex/pull/21559): switch
`/permissions` to the profile-aware TUI picker.
## Verification
- `cargo check -p codex-thread-manager-sample`
- `cargo test -p codex-core
default_permissions_can_select_builtin_profile_without_permissions_table`
- `cargo test -p codex-core
permissions_profiles_allow_direct_write_roots_outside_workspace_root`
## Summary
Move the rusty_v8 artifact production into hermetic Bazel path and bump
the `v8` crate to `147.4.0`
The new flow builds V8 release artifacts from source for Darwin and
Linux targets, publishes both the current release-compatible artifacts
and sandbox-enabled variants, and keeps Cargo consumers on prebuilt
binaries by continuing to feed the `v8` crate the archive and generated
binding files it already expects.
## Why
We need control over V8 build-time features without giving up prebuilt
artifacts for downstream Cargo builds.
Upstream `rusty_v8` already supports source-only features such as
`v8_enable_sandbox`, but its normal prebuilt release assets do not cover
every feature combination we need. Building the artifacts ourselves lets
us enable settings such as the V8 sandbox and pointer compression at
artifact build time, then publish those outputs so ordinary Cargo builds
can still consume prebuilts instead of compiling V8 locally.
This keeps the fast consumer experience of prebuilt `rusty_v8` archives
while giving us a reproducible path to ship featureful variants that
upstream does not currently publish for us.
## Implementation Notes
The Bazel graph in this PR is not copied wholesale from `rusty_v8`;
`rusty_v8`'s normal source build is still GN/Ninja-based.
Instead, this change starts from upstream V8's Bazel rules and adapts
them to Codex's hermetic toolchains and dependency layout. Where we
intentionally follow `rusty_v8`, we mirror its existing artifact
contract:
- the same `v8` crate version and generated binding expectations
- the same sandbox feature relationship, where sandboxing requires
pointer compression
- the same custom libc++ model expected by Cargo's default
`use_custom_libcxx` feature
- the same release-style archive plus `src_binding` outputs consumed by
the `v8` crate
To preserve that contract, the Bazel release path pins the libc++,
libc++abi, and llvm-libc revisions used by `rusty_v8 v147.4.0`, builds
release artifacts with `--config=rusty-v8-upstream-libcxx`, and folds
the matching runtime objects into the final static archive.
## Windows
Windows is annoyingly handled differently.
Codex's current hermetic Bazel Windows C++ platform is `windows-gnullvm`
/ `x86_64-w64-windows-gnu`, while upstream `rusty_v8` publishes Windows
prebuilts for `*-pc-windows-msvc`. Those are different ABIs, so the
Bazel graph cannot truthfully reproduce the upstream MSVC artifacts
until we add a real MSVC-targeting C++ toolchain.
For now:
- Windows MSVC consumers continue to use upstream `rusty_v8` release
archives.
- Windows GNU targets are built in-tree so they link against a matching
GNU ABI.
- The canary workflow separately exercises upstream `rusty_v8` source
builds for MSVC sandbox artifacts, but MSVC is not yet part of the
Bazel-produced release matrix.
## Validation
This PR is technically self validating through CI. I have already
published it as a release tag so the artifacts from this branch are
published to
https://github.com/openai/codex/releases/tag/rusty-v8-v147.4.0 CI for
this PR should therefore consume our own release targets. I have also
locally tested for linux and darwin.
---------
Co-authored-by: Codex <noreply@openai.com>
**Stack position:** [5 of 7]
## Summary
This PR adds `Op::ThreadSettings`, a queued settings-only update
mechanism for changing stored thread settings without starting a new
turn. It also removes the legacy `Op::OverrideTurnContext` in the same
layer, so reviewers can see the replacement and deletion together.
## Changes
- Add `Op::ThreadSettings` for settings-only queued updates.
- Emit `ThreadSettingsApplied` with the effective thread settings
snapshot after core applies an update.
- Route settings-only updates through the same submission queue as user
input.
- Migrate remaining `OverrideTurnContext` tests and callers to the
queued `Op::ThreadSettings` path.
- Delete `Op::OverrideTurnContext` from the core protocol and submission
loop.
This stack addresses #20656 and #22090.
## Stack
1. [1 of 7] [Add thread settings to
UserInput](https://github.com/openai/codex/pull/23080)
2. [2 of 7] [Remove
UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
3. [3 of 7] [Remove
UserTurn](https://github.com/openai/codex/pull/23075)
4. [4 of 7] [Placeholder for OverrideTurnContext
cleanup](https://github.com/openai/codex/pull/23087)
5. [5 of 7] [Replace OverrideTurnContext with
ThreadSettings](https://github.com/openai/codex/pull/22508) (this PR)
6. [6 of 7] [Add app-server thread settings
API](https://github.com/openai/codex/pull/22509)
7. [7 of 7] [Sync TUI thread
settings](https://github.com/openai/codex/pull/22510)
## Why
Windows can reject plugin cache upgrades when a running MCP server still
has its working directory inside the currently active plugin version.
The existing cache refresh path replaces
`plugins/cache/<marketplace>/<plugin>` as a whole, so a live handle
under the old version can make an otherwise ordinary version bump fail.
This PR keeps the existing plugin-selection model intact while making
version bumps less disruptive.
## What changed
- When installing a new version beside an existing plugin cache root,
move only the staged version directory into place instead of replacing
the whole plugin root.
- Best-effort prune older sibling version directories after the new
version is activated.
- Preserve the existing whole-root replacement path for first installs
and same-version refreshes.
- Add regression coverage for upgrading from `1.0.0` to `2.0.0` without
replacing the plugin root.
## Verification
- `cargo test -p codex-core-plugins install_with_new_version`
- `cargo fmt --package codex-core-plugins --check`
## Why
`run_turn` had accumulated the turn-scoped skill, plugin, app, MCP,
connector-selection, and analytics setup inline. That made the
orchestration path harder to scan even though the actual turn item
injection still needs to stay in `run_turn` so ordering is explicit.
## What changed
This extracts that setup into `build_skills_and_plugins`, which returns
the combined injection `ResponseItem`s and the explicitly enabled
connector IDs. `run_turn` now keeps the required orchestration pieces:
context update recording, user input handling, connector selection
merge, and the explicit per-item `record_conversation_items` calls for
injection items.
The refactor keeps the change LOC-neutral in `core/src/session/turn.rs`
and preserves the existing response-item based injection path.
## Validation
- `cargo test -p codex-core collect_explicit_app_ids_from_skill_items`
- `just fix -p codex-core`
**Stack position:** [1 of 7]
## Summary
The first three PRs in this stack are a cleanup pass before the actual
thread settings API work.
Today, core has several overlapping "user input" ops: `UserInput`,
`UserInputWithTurnContext`, and `UserTurn`. They differ mostly in how
much next-turn state they carry, which makes the later queued thread
settings update harder to reason about and review.
This PR starts that cleanup by adding the shared
`ThreadSettingsOverrides` payload and allowing `Op::UserInput` to carry
it. Existing variants remain in place here, so this layer is mostly a
behavior-preserving API shape change plus mechanical constructor
updates.
## End State After PR3
By the end of PR3, `Op::UserInput` is the only "user input" core op. It
can carry optional thread settings overrides for callers that need to
update stored defaults with a turn, while callers without updates use
empty settings. `Op::UserInputWithTurnContext` and `Op::UserTurn` are
deleted.
## End State After PR5
By the end of PR5, core will have only two ops for this area:
- `Op::UserInput` for user-input-bearing submissions.
- `Op::ThreadSettings` for settings-only updates.
## Stack
1. [1 of 7] [Add thread settings to
UserInput](https://github.com/openai/codex/pull/23080) (this PR)
2. [2 of 7] [Remove
UserInputWithTurnContext](https://github.com/openai/codex/pull/23081)
3. [3 of 7] [Remove
UserTurn](https://github.com/openai/codex/pull/23075)
4. [4 of 7] [Placeholder for OverrideTurnContext
cleanup](https://github.com/openai/codex/pull/23087)
5. [5 of 7] [Replace OverrideTurnContext with
ThreadSettings](https://github.com/openai/codex/pull/22508)
6. [6 of 7] [Add app-server thread settings
API](https://github.com/openai/codex/pull/22509)
7. [7 of 7] [Sync TUI thread
settings](https://github.com/openai/codex/pull/22510)
## Summary
- mark `ToolSearch` as removed and ignore stale config writes for its
legacy key
- make search tool exposure depend only on model capability, not a
feature toggle
- remove app-server enablement support and prune now-obsolete test
coverage/setup
## Verification
- `cargo test -p codex-features`
- `cargo test -p codex-tools`
- `cargo test -p codex-core search_tool_requires_model_capability`
- `cargo test -p codex-app-server experimental_feature_enablement_set_`
## Notes
- This keeps the legacy config key as a no-op for compatibility while
removing the ability to toggle the behavior off cleanly.
- No developer-facing docs update outside the touched app-server README
was needed.
Deletes the skill env var dependency prompt feature and its runtime
path. env_var entries in skill dependency metadata are now silently
ignored during skill loading.
## Why
Compaction now installs replacement history inside the session, but the
turn and compaction callers were still reaching into
`ModelClientSession` to reset websocket transport state after that
install. That made a transport-level reset part of the compaction API
even though websocket incremental request selection already checks
whether the next request is a strict extension of the previous one and
falls back to a full `response.create` when it is not.
## What changed
- Removed the compaction-side calls to `reset_websocket_session` from
`compact.rs` and `session/turn.rs`.
- Simplified pre-sampling and mid-turn compaction helpers so they return
`CodexResult<()>` instead of carrying a reset flag.
- Made `ModelClientSession::reset_websocket_session` private to
`client.rs`, leaving only the websocket timeout recovery path inside the
client as a caller.
## Validation
- `cargo test -p codex-core --test all
responses_websocket_creates_on_non_prefix`
- `cargo test -p codex-core --test all
steered_user_input_waits_for_model_continuation_after_mid_turn_compact`
- `cargo test -p codex-core --test all
pre_sampling_compact_runs_on_switch_to_smaller_context_model`
## Why
The v2 app-server permission profile fields are experimental, but the
previous migration kept a legacy object payload for profile selection.
That made clients aware of server-owned `activePermissionProfile`
metadata such as `extends`, and it kept a
`legacy_additional_writable_roots` path even though
`runtimeWorkspaceRoots` now owns runtime workspace-root selection.
This PR makes the client contract match the intended model: clients
select a permission profile by id, and the server resolves and reports
active profile provenance in response payloads.
Follow-up to #22611.
## What Changed
- Changed `thread/start`, `thread/resume`, `thread/fork`, and
`turn/start` permission profile selection to plain profile id strings.
- Changed `command/exec.permissionProfile` to a plain profile id string
for the same client/server ownership split.
- Removed `PermissionProfileSelectionParams` and the legacy `{ type:
"profile", modifications: [...] }` compatibility deserializer.
- Updated app-server, TUI, and `codex exec` call sites to send only ids,
while keeping `activePermissionProfile` as server response metadata.
- Updated app-server docs and schema fixtures for the revised
`command/exec.permissionProfile` shape.
## Verification
- `cargo test -p codex-app-server-protocol`
- `RUST_MIN_STACK=8388608 cargo test -p codex-app-server`
- `cargo test -p codex-exec`
- `RUST_MIN_STACK=8388608 cargo test -p codex-tui`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23360).
* #23368
* __->__ #23360
## Why
- Follows #20949.
- The above moved `thread_source` attribution from the reducer to
explicit caller provided metadata
- The `codex exec` path still omitted this metadata, leaving
exec-created threads without `thread_source`
## What Changed
- Ensures exec threads are marked as user created (`thread_source =
"user"`)
- Preserves thread-source metadata in exec’s startup session event
## Verification
- Updated unit tests to validate exec `thread_source` propagation.
- `cargo +1.93.0 test -p codex-exec --manifest-path codex-rs/Cargo.toml`
- `cargo +1.93.1 build -p codex-cli --manifest-path codex-rs/Cargo.toml`
- Validated locally with a freshly built `codex exec` run:
- Startup logs showed `thread_source: Some(User)`.
- Rollout metadata recorded `"thread_source":"user"`.
## Why
Older iTerm2 builds can be detected as supporting the image transport
that terminal pets use, but in practice they fail to render the pet flow
correctly. Instead of silently attempting image rendering, Codex should
tell the user that their iTerm2 version is too old and that upgrading is
the fix.
## What Changed
- gate iTerm2 pet auto-detection on version `3.6.0` or newer
- show a dedicated upgrade message for older or unknown iTerm2 versions
instead of the generic unsupported-terminal warning
- keep the existing generic unsupported-terminal path for non-iTerm
terminals
- add regression coverage for iTerm2 version parsing and the old-iTerm
warning path
## How to Test
1. Start Codex in iTerm2 3.6 or newer.
2. Run `/pets`.
3. Confirm the pets picker opens instead of showing a warning.
4. Start Codex in an older iTerm2 build, or exercise the equivalent test
path.
5. Run `/pets`.
6. Confirm Codex warns that pets require iTerm2 3.6 or newer and tells
the user to upgrade.
7. Also verify that a non-iTerm unsupported terminal still shows the
generic unsupported-terminal message.
Targeted tests:
- `cargo test -p codex-terminal-detection`
- `cargo test -p codex-tui pets::`
- `cargo test -p codex-tui slash_pets_on_unsupported_terminal`
- `cargo test -p codex-tui slash_pets_on_old_iterm2`
## Why
Pending model input was split across `Session`, `TurnState`, and the
agent mailbox. That made it easy for new paths to manage queued user
input or mailbox delivery outside the intended ownership boundary.
This PR consolidates the model-facing input lifecycle behind the session
input queue so turn-local pending input, next-turn queued items, and
mailbox delivery coordination are owned in one place.
## What Changed
- Added `session/input_queue.rs` to own pending input queues and mailbox
delivery coordination.
- Removed the standalone `agent/mailbox.rs` channel wrapper and store
mailbox items directly in the input queue.
- Moved pending-input mutations off `TurnState`; `TurnState` now exposes
the queue-owned storage directly for now.
- Routed abort cleanup, mailbox delivery phase changes, next-turn queued
items, and active-turn pending input through `InputQueue`.
- Boxed stack-heavy agent resume/fork startup futures that the refactor
pushed over the default test stack.
- Updated session, task, goal, stream-event, and multi-agent call sites
and tests to use the new queue ownership.
## Verification
- `cargo test -p codex-core --lib agent::control::tests`
- `cargo test -p codex-core --lib
agent::control::tests::resume_closed_child_reopens_open_descendants --
--exact`
- `cargo test -p codex-core --lib
agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns
-- --exact`
- `cargo test -p codex-core --lib
agent::control::tests::resume_thread_subagent_restores_stored_nickname_and_role
-- --exact`
- `cargo test -p codex-core` was also run; it completed with 1814
passed, 4 ignored, and one timeout in
`agent::control::tests::resume_thread_subagent_restores_stored_nickname_and_role`,
which passed when rerun in isolation.
Adding the id of the plugin that contains the MCP (if any) so we can
apply filters at plugin level.
## Summary
- carry the plugin owner into MCP runtime provenance
- attach `plugin_id` to outbound plugin-backed MCP tool-call `_meta`
- avoid misattributing user-configured MCP servers that shadow plugin
server names
## Testing
- `just fmt`
- `just fix -p codex-mcp`
- `just fix -p codex-core`
- `cargo test -p codex-mcp`
- `cargo test -p codex-core
plugin_mcp_tool_call_request_meta_includes_plugin_id`
- `cargo test -p codex-core
to_mcp_config_omits_plugin_id_when_user_server_shadows_plugin_mcp`
- `cargo test -p codex-core
rebuild_preserving_session_layers_refreshes_plugin_derived_mcp_config`
- `git diff --check`
## Notes
- Attempted `cargo test -p codex-core`; it aborted in
`agent::control::tests::resume_agent_from_rollout_skips_descendants_when_parent_resume_fails`
with a stack overflow before the full suite completed.
## Why
`TurnContextItem` is the durable baseline used to reconstruct context
diffs across resume/fork. Most of the old persisted-only fields on it
are no longer read, so keeping them in rollout snapshots adds schema
surface and state that can drift without affecting reconstruction.
`summary` is the exception: older Codex versions require it to
deserialize `turn_context` records, so keep writing a default
compatibility value until that schema surface can be removed safely.
## What changed
- Removed the unused persisted fields from `TurnContextItem`: trace ids,
user/developer instructions, output schema, and truncation policy.
- Kept `summary` with a compatibility comment and made
`TurnContext::to_turn_context_item` write `ReasoningSummary::Auto`
instead of live turn state.
- Updated rollout/context reconstruction fixtures for the retained
summary field.
## Verification
- `cargo test -p codex-protocol --lib turn_context_item`
- `cargo test -p codex-rollout
resume_candidate_matches_cwd_reads_latest_turn_context`
- `cargo test -p codex-state turn_context`
- `cargo test -p codex-core --lib
new_default_turn_captures_current_span_trace_id`
- `cargo test -p codex-core --lib
record_initial_history_resumed_turn_context_after_compaction_reestablishes_reference_context_item`
- `cargo test -p codex-core --test all
emits_warning_when_resumed_model_differs`
- `git diff --check`
## Why
The Python SDK depends on `openai-codex-cli-bin` runtime wheels being
installable on the Linux hosts our users actually run. The release
workflow currently tags the Linux runtime artifacts as `musllinux_*`,
which makes pip ignore them on normal glibc distributions even though
the bundled Rust executables are intended to run there.
## What changed
- Tag the Linux runtime wheels as `manylinux_2_17_aarch64` and
`manylinux_2_17_x86_64` instead of `musllinux_1_1_*`.
- Keep the existing runtime wheel build and publish flow unchanged
otherwise.
## Verification
- Confirmed the wheel-tag issue against the PyPA platform-tag rules for
`manylinux` vs `musllinux`.
- This PR is now intentionally scoped to the tag correction only; the
broader Python runtime release workflow has already landed on `main`
through the merged stack.
## Follow-up
After publishing the next alpha from this branch, install the
SDK/runtime in a fresh glibc Linux environment and confirm pip resolves
the tagged Linux wheel as expected.
Co-authored-by: Codex <noreply@openai.com>
## Description
This PR makes `codex remote-control` behave like a foreground CLI
command by default. Running it now starts remote control, waits for
readiness, prints a clear status message with the machine name, and
stays alive until Ctrl-C.
Users who want daemon behavior can use `codex remote-control start`, and
`codex remote-control stop` now prints concise human-readable output.
`--json` remains available for scripts.
Implementation-wise, this now verifies the real app-server state instead
of just assuming startup worked. The CLI starts or connects to
app-server, probes its control socket, calls the `remoteControl/enable`
API, and waits for the remote-control status response/notification
before printing success.
For daemon mode, `codex remote-control start` also reports which managed
app-server binary was used, including its path and best-effort `codex
--version`, so failures are easier to diagnose.
## Examples
Example output:
```
> codex remote-control
Starting app-server with remote control enabled...
This machine is available for remote control as com-97826.
Press Ctrl-C to stop.
```
Error case using daemon (currently expected based on our publicly
released CLI version):
```
> ./target/debug/codex remote-control start
Starting app-server daemon with remote control enabled...
Error: app server did not become ready on /Users/owen/.codex/app-server-control/app-server-control.sock
Daemon used app-server:
path: /Users/owen/.codex/packages/standalone/current/codex
version: 0.130.0
Managed app-server stderr (/Users/owen/.codex/app-server-daemon/app-server.stderr.log):
error: unexpected argument '--remote-control' found
Usage: codex app-server [OPTIONS] [COMMAND]
For more information, try '--help'.
Caused by:
0: failed to connect to /Users/owen/.codex/app-server-control/app-server-control.sock
1: No such file or directory (os error 2)
```
## What changed
- `codex remote-control` now runs remote control in the foreground and
prints a Ctrl-C stop hint.
- `codex remote-control start` starts the daemon and waits for remote
control readiness before reporting success.
- `codex remote-control stop` reports stopped/not-running status in
plain language.
- Startup failures now include recent managed app-server stderr to make
daemon issues easier to diagnose.
- Added coverage for CLI output, readiness waiting, foreground shutdown,
and stderr log tailing.
## Why
Recent `rust-ci-full` failures were dominated by transient Windows
timeout clusters in process-heavy tests such as `suite::resume`,
`suite::cli_stream`, `suite::auth_env`,
`start_thread_uses_all_default_environments_from_codex_home`, and
`connect_stdio_command_initializes_json_rpc_client_on_windows`.
The goal here is to make those known flaky paths less likely to fail
full CI without relaxing the global nextest timeout policy.
## What changed
- Enable one global nextest retry with `retries = 1` so a single
transient failure can recover.
- Add a `windows_process_heavy` test group with `max-threads = 2` for
the recurring Windows subprocess/session-heavy timeout families.
- Add Windows-only slow-timeout overrides for that process-heavy group.
- Add a narrower Windows-only timeout override for
`start_thread_uses_all_default_environments_from_codex_home`, which
still exceeded the broader Windows bucket in both Windows full-CI lanes.
- Increase the `rust-ci-full` nextest job timeout from `45m` to `60m` so
Windows ARM64 still has job-level headroom after retries and targeted
per-test timeout increases.
- Keep the global `slow-timeout` unchanged at `15s`.
## Validation
Validated through `rust-ci-full` GitHub Actions reruns on this PR.
Observed improvement on the tuned Windows lanes:
- Windows x64 went from `5 timed out` to `0 timed out`.
- Windows ARM64 went from `2 timed out` to `0 timed out`.
- `start_thread_uses_all_default_environments_from_codex_home` recovered
as a flaky pass on Windows ARM64 instead of timing out.
The remaining failing tests in those runs were unrelated hard failures
outside this nextest timeout tuning.
## Why
Extensions that need to track runtime progress currently have no typed
host signal for tool execution. The goal extension in particular needs
to observe tool attempts without inspecting tool payloads, owning tool
implementations, or staying coupled to core-only runtime plumbing.
This adds a narrow lifecycle contributor API for host-owned tool
execution: extensions can observe when an accepted tool call starts and
how it finishes, while policy hooks and tool handlers continue to own
payload rewriting, blocking, and execution.
Relevant code:
-
[`ToolLifecycleContributor`](3ad2850ffc/codex-rs/ext/extension-api/src/contributors.rs (L119))
defines the extension-facing observer contract.
-
[`tool_lifecycle.rs`](3ad2850ffc/codex-rs/ext/extension-api/src/contributors/tool_lifecycle.rs)
defines the typed start/finish inputs, source, and outcome enums.
- [`notify_tool_start` /
`notify_tool_finish`](3ad2850ffc/codex-rs/core/src/tools/lifecycle.rs)
bridges core tool dispatch into the extension registry.
## What Changed
- Added `ToolLifecycleContributor` to `codex-extension-api`, including:
- `ToolStartInput`
- `ToolFinishInput`
- `ToolCallSource`
- `ToolCallOutcome`
- Added registration and lookup support on `ExtensionRegistryBuilder` /
`ExtensionRegistry`.
- Wired core tool dispatch to notify lifecycle contributors for:
- accepted tool starts
- completed tool calls, including the tool output success marker
- pre-tool-use blocks
- failures before or after the handler runs
- cancellation/abort in the parallel tool path
- Registered the goal extension as a lifecycle contributor and added the
outcome filter it will use for goal progress accounting.
## Test Coverage
- Added `dispatch_notifies_tool_lifecycle_contributors` to cover
lifecycle notification ordering and outcomes for successful and
handler-failed tool calls.
## Why
Some tool providers, especially MCP servers and dynamic tool sources,
can supply schema nodes that omit `type` and have no recognized JSON
Schema shape hints. Previously, `sanitize_json_schema` filled those
unknown nodes in as `string`, which made the schema parseable but
invented a scalar constraint that the provider did not specify. For
description-only fields, that could incorrectly steer tool arguments
away from the provider's actual accepted shape.
The Responses API accepts permissive empty schemas such as `{}` at
nested property positions, so Codex should preserve that permissive
meaning instead of coercing unknown schema nodes into a misleading
scalar type.
## What Changed
- Changed the no-hints fallback in `codex-rs/tools/src/json_schema.rs`
to clear unrecognized object schema nodes to `{}`.
- Empty schemas now remain `{}` rather than becoming `type: "string"`.
- Description-only or otherwise metadata-only nested property schemas
now become `{}` while surrounding object/array/string/number inference
still applies when recognized hints are present.
- Updated `codex-tools` and `codex-core` tests to cover top-level empty
schemas, nested empty schemas, metadata-only malformed schemas, dynamic
tools, and MCP tool specs.
## Verification
- `cargo test -p codex-tools`
- `cargo test -p codex-core
test_mcp_tool_property_missing_type_defaults_to_empty_schema`
- Manually verified the real Responses API behavior for both
empty-schema positions:
- Top-level function `parameters: {}` is accepted and echoed back as
`{"type":"object","properties":{}}`; when forced to call the tool,
Responses emitted empty object arguments: `"arguments": "{}"`.
- Nested property schema `{}` is accepted and preserved as `{}`; when
forced to call a tool with `metadata.extra`, Responses emitted
`"arguments": "{\"metadata\":{\"extra\":\"codex schema sanitizer
behavior\"}}"`.
## Summary
- make `load_global_instructions` read through an `ExecutorFileSystem`
- call global AGENTS reads with explicit `LOCAL_FS` so they stay tied to
local codex-home state
## Validation
- `bazel test --bes_backend= --bes_results_url=
--test_filter=instruction_sources_include_global_before_agents_md_docs
//codex-rs/core:core-unit-tests` on `dev`
## Why
`experimentalFeature/list` reports effective feature enablement, but
currently does not resolve it against a working directory where
project-local config.toml files can exist and toggle on/off features
when merged into the effective config after resolving the various config
layers. That means we effectively (and incorrectly) ignore features set
in project-local config.
To address that, this PR exposes an optional `thread_id` param which
allows us to load the thread's `cwd.
## Testing
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server experimental_feature_list`
## Why
The session picker already supports typed search, but it ignored
bracketed paste events entirely. On macOS terminals this makes pasted
text look like a no-op on the resume screen, which is especially
noticeable when a user wants to paste part of a thread name, branch, or
path into the search field.
## What Changed
- route `TuiEvent::Paste(String)` into the session picker instead of
dropping it
- normalize pasted search text into a single-line query by collapsing
whitespace
- ignore whitespace-only pastes
- reuse the existing `set_query(...)` path so pasted searches keep the
same filtering and pagination behavior as typed input
- add focused tests for append behavior, whitespace normalization,
whitespace-only paste, and the existing search-loading path
This PR is stacked on top of #23234 and contains only the net change
relative to `etraut/clarify-resume-hints`.
## How to Test
1. Start Codex in a terminal that emits bracketed paste, for example
iTerm2 on macOS.
2. Open the resume picker so the search UI is visible.
3. Copy a term that should match one of the visible sessions, then paste
it into the picker.
4. Confirm the query updates immediately and the list filters as if the
text had been typed.
5. Also verify that pasting text with newlines or tabs still produces a
usable single-line search query.
6. Also verify that normal typed search still works and that `Esc` still
clears the query / exits as before.
Targeted tests:
- `cargo test -p codex-tui`
---------
Co-authored-by: Eric Traut <etraut@openai.com>
## Problem
This addresses several user-reported cases where active goals were
paused even though the user had not explicitly asked for that
transition:
- the guardian approval-review circuit breaker interrupted a turn and
implicitly paused the goal
- a shutdown in one app-server instance could pause a goal while a
second instance was still actively running the same thread
- steering-style interrupts could also pause the goal even though they
are meant to redirect work, not stop the goal lifecycle
The common problem was that core treated `TurnAbortReason::Interrupted`
as an implicit request to transition the persisted goal to `paused`.
That made unrelated interrupt paths mutate goal state as a side effect,
and in the multi-app-server case it allowed stale process teardown to
pause a live goal owned by another running client.
After this change, transitioning a goal to `paused` is always an
explicit action performed by a client or another intentional goal-state
mutation. It is never an implicit transition triggered by generic
interrupt handling.
Refs #22884.
## What changed
- Remove the goal runtime path that paused active goals after
interrupted task aborts.
- Drop the now-unused abort reason from `GoalRuntimeEvent::TaskAborted`.
- Update the focused regression coverage so an interrupted active goal
still accounts usage but remains `active`.
Fixes#22300.
## Summary
MCP tool calls can appear in thread history while still in progress.
During replay, `handle_thread_item` routed every
`ThreadItem::McpToolCall` to the completion handler, so an in-progress
item with no result or error was rendered as `MCP tool call completed
without a result`.
This updates replay handling to mirror command executions: `InProgress`
MCP calls go through `on_mcp_tool_call_started`, while completed and
failed calls continue through the completion path.
## Validation
- `cargo test -p codex-tui
replayed_in_progress_mcp_tool_call_stays_active`
## Why
Fixes#21894.
When the TUI handles an MCP elicitation, the request payload already
includes the thread that generated the elicitation.
`ChatWidget::handle_elicitation_request_now` was ignoring that value and
using the currently visible chat thread instead. In a multi-session TUI,
that can send `resolve_elicitation` to an older visible thread rather
than the session that owns the pending elicitation, producing
`elicitation request not found` and leaving the prompt unresolved.
## What changed
- Parse `McpServerElicitationRequestParams.thread_id` in the ChatWidget
elicitation handler and use it for app-link, form, fallback approval,
and auto-decline resolution paths.
- Keep the existing visible-thread fallback only for malformed request
payloads with an invalid thread id.
- Update the invalid URL elicitation regression test so the visible
thread and request thread intentionally differ.
Addresses #23181
## Why
Renamed threads can share names, so hints that suggest resuming directly
by name are ambiguous. Issue #23181 asks for the picker hint to include
the thread name and thread ID in parens so users can disambiguate
safely.
## What
- Adds a shared resume hint formatter for named threads: run `codex
resume`, then select `<name> (<thread-id>)`.
- Uses that hint for /rename confirmations, TUI session summaries, and
CLI/TUI exit messages.
- Keeps direct `codex resume <thread-id>` guidance for unnamed threads.
## Verification
Manually verified that message after `/rename` and after `/exit` include
session ID in parens.
---------
Co-authored-by: Felipe Coury <felipe.coury@openai.com>
Addresses #22833, #22245, #23067
## Why
`/goal` can keep synthesizing turns even when the next turn cannot make
meaningful progress. Hard usage exhaustion can replay failing turns, and
repeated permission or external-resource blockers can keep burning
tokens while waiting for user or system intervention.
## What changed
- Add resumable `blocked` and `usageLimited` goal states. As with
`paused`, goal continuation stops with these states.
- Move to `usageLimited` after usage-limit failures.
- Allow the built-in `update_goal` tool to set `blocked` only under
explicit repeated-impasse guidance. Updated goal continuation prompt to
specify that agent should use `blocked` only when it has made at least
three attempts to get past an impasse.
Most of the files touched by this PR are because of the small app server
protocol update.
## Validation
I manually reproduced a number of situations where an agent can run into
a true impasse and verified that it properly enters `blocked` state. I
then resumed and verified that it once again entered `blocked` state
several turns later if the impasse still exists.
I also manually reproduced the usage-limit condition by creating a
simulated responses API endpoint that returns 429 errors with the
appropriate error message. Verified that the goal runtime properly moves
the goal into `usageLimited` state and TUI UI updates appropriately.
Verified that `/goal resume` resumes (and immediately goes back into
`ussageLImited` state if appropriate).
## Follow-up PRs
Small changes will be needed to the GUI clients to properly handle the
two new states.
# Summary
Before this change, the sample plugin creator could emit
placeholder-heavy manifests that fail workspace sharing, and it chose a
repo-local marketplace implicitly whenever it ran from inside a git
checkout.
This PR makes generated plugins share-ready by default. It switches
creation to the personal marketplace unless the caller explicitly opts
into repo-local paths, adds a validator that mirrors the workspace
plugin ingestion contract, and updates the skill prompt and docs to
describe the real flow.
The goal is to stop malformed generated plugins before they reach
sharing and to make the default placement match the personal marketplace
behavior users expect.
## Changes
- Generate share-safe plugin manifests instead of `[TODO: ...]`
placeholder payloads.
- Default plugin and marketplace creation to `~/plugins` and
`~/.agents/plugins/marketplace.json`.
- Keep repo-local marketplace creation available through explicit
`--path` and `--marketplace-path` arguments.
- Add `validate_plugin.py` to check manifests, companion files, skill
frontmatter, skill agent YAML, asset paths, and backend-shaped contracts
before sharing.
- Refresh the plugin creator skill text, reference docs, and default
prompt to describe validation and the personal default.
## Design decisions
- The validator tracks the workspace ingestion schema directly,
including the required `defaultPrompt` alias handling and skill
`agents/openai.yaml` checks.
- The validator keeps one intentional extra preflight rule: leftover
`[TODO: ...]` placeholders are rejected before sharing even when a
single placeholder would not independently violate backend type
validation.
- Repo-local creation stays possible, but it is now explicit instead of
cwd-sensitive.
## Testing
Tests: targeted Python syntax checks, plugin skill validation, staged
diff whitespace validation, 15 generated plugin smoke runs, backend
manifest-schema acceptance for all 15 generated bundles, and a git-repo
cwd regression proving the creator still writes to the personal
marketplace by default.
## Why
`rust-ci-full` failures currently leave downstream investigation
reconstructing basic test facts from raw logs. `cargo nextest` can emit
standard JUnit XML for each lane, which gives us a small structured
artifact for post-run failure analysis without changing the test
execution model.
## What changed
- enable nextest JUnit output in `codex-rs/.config/nextest.toml`
- upload the lane-scoped JUnit XML artifact from each `rust-ci-full`
test lane
## Verification
- `rust-ci-full` run `26018931531` on head
`52d77c60e79b36859d944ef28a36b014055c5c48` produced JUnit artifacts for
macOS, Linux x64 remote, Windows x64, and Windows ARM64 test lanes
- `rust-ci-full` run `26021241006` on the same head produced the missing
Linux ARM JUnit artifact after the first run lost that runner before
export
- downloaded all five lane JUnit artifacts and verified each contains
non-empty test counters and failure data
## Why
The legacy Windows sandbox still carried a `persist_aces` mode switch,
even though the only path that meaningfully applies filesystem ACEs
today is `workspace-write`, which already uses the persistent behavior.
Legacy read-only sessions rely on the read-only capability SID rather
than per-command filesystem ACE mutation, so the temporary cleanup
branch had become conceptual overhead without a corresponding behavioral
need.
Removing that split makes the ACL lifecycle match the current sandbox
model more directly and trims the guard/revocation plumbing from the
legacy launcher paths.
## What changed
- Removed the `persist_aces` parameter from legacy ACL preparation.
- Made legacy deny-read handling always use the persistent
reconciliation path.
- Dropped guard tracking and post-exit ACE revocation from both capture
and unified-exec legacy flows.
- Kept workspace `.codex` / `.agents` protection tied directly to
`WorkspaceWrite` instead of an intermediate persistence flag.
## Verification
- `cargo fmt -p codex-windows-sandbox`
- `git diff --check`
- `cargo test -p codex-windows-sandbox`
- 85 passed, 2 ignored, 2 (unrelated) failed locally.
## Why
`TurnDiffTracker` computes a display root so turn diffs can be rendered
repo-relative. For remote exec-server turns, the selected turn `cwd` may
exist only inside the selected environment, but `run_turn` was
discovering the git root through the local host filesystem. When that
lookup failed, nested remote-session diffs fell back to the nested `cwd`
and showed `/tmp/...`-prefixed paths instead of repo-relative paths.
## What changed
- Resolve the diff display root from the primary selected turn
environment when one exists, using that environment's filesystem and
`cwd`.
- Add `codex_git_utils::get_git_repo_root_with_fs(...)` so git-root
discovery can run against an `ExecutorFileSystem`, including remote
environments.
- Reuse that helper from `resolve_root_git_project_for_trust(...)` and
add coverage for `.git` gitdir-pointer detection.
## Validation
- Devbox Bazel: `//codex-rs/core:core-unit-tests
--test_filter=get_git_repo_root_with_fs_detects_gitdir_pointer`
- Devbox Docker-backed remote-env repro: `//codex-rs/core:core-all-test
--test_filter=apply_patch_turn_diff_paths_stay_repo_relative_when_session_cwd_is_nested`
## Why
Ctrl+C can take a noticeable amount of time to finish when the TUI is
waiting for the app-server thread shutdown path to complete. Before this
change, the UI could look like it had not accepted the shutdown request
because the composer and cursor remained in their normal interactive
state during that wait.
This PR makes the accepted shutdown visible immediately. It does not add
an artificial sleep or change the shutdown timeout; it only draws one
final feedback frame before continuing through the existing shutdown
flow.
## What Changed
- On `ExitMode::ShutdownFirst`, the TUI now renders shutdown feedback
before awaiting the existing thread shutdown future.
- The bottom pane disables composer input, which hides the cursor
through the existing disabled-input cursor path.
- The composer shows `Shutting down...` as the disabled input hint and
suppresses footer content so the shutdown acknowledgement is not
competing with shortcut/status text.
- The logout path uses the same feedback path before shutting down.
## How to Test
1. Start Codex from this branch.
2. Press `Ctrl+C` to request shutdown.
3. If shutdown takes long enough to observe, confirm the composer
changes to `› Shutting down...`, the cursor disappears, and no footer
hint is rendered below it.
4. Regression check: repeat with text already typed in the composer and
confirm the visible row still switches to `Shutting down...` while the
draft remains preserved internally until the process exits.
Targeted tests:
- `cargo test -p codex-tui
shutdown_in_progress_disables_input_and_uses_hint_without_footer`
- `cargo test -p codex-tui bottom_pane::footer::tests::`
## Local Validation Note
`cargo test -p codex-tui` still aborts in
`app::tests::discard_side_thread_removes_agent_navigation_entry` with a
stack overflow. That same test also failed when run alone locally, and
the failure appears unrelated to this shutdown feedback path.
## Why
Windows release artifacts currently import `VCRUNTIME140.dll` and
`VCRUNTIME140_1.dll`. That becomes observable on clean Windows machines
that do not already have the VC++ runtime available globally:
- Desktop Store launches can fail after the app relocates `codex.exe`
out of `WindowsApps`, which means an MSIX-level VCLibs dependency does
not protect the relocated CLI/app-server process.
- The npm CLI path reproduces the same missing-DLL startup failure when
`System32\vcruntime140_1.dll` is hidden and `PATH` is stripped of
incidental fallback copies.
In that setup, the existing Windows binary exits with `0xC0000135` /
`-1073741515` before Codex code runs.
## What changed
- Add `-C target-feature=+crt-static` to the existing MSVC-only Cargo
rustflags in `codex-rs/.cargo/config.toml`.
- Preserve the existing `/STACK:8388608` linker setting in the same
target block.
This keeps the change scoped to Windows MSVC builds and avoids altering
non-Windows or GNU target behavior.
## Verification
I built an x64 Windows release probe with static CRT linkage and the
normal 8 MiB stack reserve, then verified:
- `dumpbin /dependents codex.exe` no longer reports `VCRUNTIME140.dll`
or `VCRUNTIME140_1.dll`.
- `dumpbin /headers codex.exe` reports `800000 size of stack reserve`.
- With `System32\vcruntime140_1.dll` hidden and `PATH` stripped to
Windows system directories only:
- the old npm CLI path exits `-1073741515`
- the rebuilt static-CRT `codex.exe --version` succeeds with exit code
`0`
- the rebuilt TUI starts successfully
I also confirmed `codex.exe app-server --listen ws://127.0.0.1:0` starts
and binds normally with the static-CRT artifact.
## Why
The client and tool pipeline still carried compatibility code for legacy
structured shell output. Current shell and apply_patch responses are
already plain text for model consumption, so keeping a
JSON-serialization path plus shell-item rewrite logic makes the request
formatter and tests preserve a format we do not need anymore.
## What Changed
- Removed the client-side shell output rewrite from
`core/src/client_common.rs`.
- Removed the structured exec-output formatter and the shell `freeform`
switch so tool emitters use one model-facing formatter.
- Collapsed apply_patch/shell serialization tests around the remaining
plain-text output expectations and removed duplicate one-variant
parameterized cases.
- Kept the `ApplyPatchModelOutput::ShellCommandViaHeredoc` compatibility
input shape, but no longer treats it as a separate output-format mode.
## Validation
- `cargo test -p codex-core client_common`
- `cargo test -p codex-core shell_serialization`
- `cargo test -p codex-core apply_patch_cli`
- `just fix -p codex-core`
## Documentation
No external Codex documentation update is needed.
## Why
Codex TUI startup still feels slower than 0.117.0 after the app-server
move in 0.118.0. A visible chunk of launch-to-input latency comes from
serial terminal startup probes: cursor position, keyboard enhancement
support, and default foreground/background color queries can each wait
on terminal responses before the first usable frame.
Refs #16335.
## What
This PR batches the terminal startup probes into one bounded probe. It
also reuses the probed cursor position and default colors during TUI
setup, fast-paths the primary-device-attributes fallback as keyboard
enhancement unsupported, and keeps lightweight startup timing logs for
future tuning.
The startup telemetry is intentionally left in production: it records
phase timings for terminal probes and initial-frame scheduling so future
startup regressions can be diagnosed from normal logs rather than
re-adding one-off debug instrumentation.
## Benchmark
In the local pty startup benchmark, the pre-optimization `main` baseline
was about 250.5ms median from launch to accepted chat input. This
probe-only branch measured about 152ms median, for an approximate
savings of 95-100ms.
## Stack
1. [#23175: [1 of 2] Optimize TUI startup terminal
probes](https://github.com/openai/codex/pull/23175) — this PR
2. [#23176: [2 of 2] Start fresh TUI thread in
background](https://github.com/openai/codex/pull/23176) — layered on
this PR
## Verification
- `cargo test -p codex-tui`
Addresses #22778
## Summary
Provider deployments such as Bedrock manage rate limits and billing
outside ChatGPT, so the `/status` link to the ChatGPT usage page is
irrelevant and confusing for those users. Custom providers that are
explicitly configured to use OpenAI/ChatGPT auth still point at
OpenAI-backed usage, so they should keep the link.
## Changes
- Render the ChatGPT usage note only when the configured provider uses
OpenAI auth.
- Keep the note hidden when `/status` displays a provider such as
Bedrock that manages limits elsewhere.
- Add regression coverage for both Bedrock and a custom OpenAI-auth
proxy provider.
## Manual Repro
1. Configure Codex with a non-OpenAI-auth provider, for example
`model_provider = "amazon-bedrock"`.
2. Start the TUI and run `/status`.
3. Confirm the status card shows the custom provider, for example `Model
provider: Amazon Bedrock`, and does not show
`https://chatgpt.com/codex/settings/usage`.
4. Configure a custom provider that proxies to OpenAI and has
OpenAI/ChatGPT auth enabled.
5. Run `/status` again and confirm the ChatGPT usage link appears for
that OpenAI-auth provider.
## Summary
Fixes#22726.
After a Responses stream disconnect, the live TUI could keep accepting
prompts while leaving partially streamed assistant output in its
transient streaming-cell form. That made fenced diffs or SVG/XML-like
content appear as raw transcript text until the user closed the TUI and
resumed the same session, which rebuilt the transcript from saved
history.
This change finalizes the active answer stream before generic
failed-turn cleanup clears the stream controller, so the live transcript
takes the same source-backed markdown consolidation path as a successful
turn.
## Reviewer repro
1. Start a local Codex TUI session.
2. Trigger an assistant turn that streams markdown content, especially a
fenced diff or SVG/XML-like block.
3. Force or encounter a non-retry stream disconnect before the turn
completes.
4. Continue using the same still-open TUI session.
5. Before this fix, the live history can stay raw/plain even though
`codex resume` renders the same session normally.
6. After this fix, the failed-turn path consolidates the partial stream
before rendering the error, so the live TUI keeps normal transcript
rendering.
## Why
`codex exec resume` should have the same structured-output support as
top-level `codex exec`. Without `--output-schema`, multi-turn automation
has to choose between resumed session context and schema-validated JSON
output.
Fixes#22998.
## What changed
- Marked `--output-schema` as a global `codex exec` flag so it can be
passed after `resume`.
- Reused the existing output schema plumbing so resumed turns attach the
schema to the final response request while preserving session context.
## Why
After turning Fast mode off in the TUI, returning from a side thread
could make `Fast` appear again in the main chat widget. The opt-out
itself was still persisted; the display was being rebuilt from stale
cached `ThreadSessionState` data, which made it look like Fast had been
re-enabled.
Fixes#23104.
## What changed
- Keep the active thread's cached `service_tier` in sync whenever the
user persists a service-tier selection.
- Update both the primary-thread snapshot and the thread event store so
restored TUI state reflects the current tier.
- Add a focused regression test for clearing a cached Fast tier.
## Manual repro
1. Start a TUI session where `Fast` is enabled by default.
2. Run `/fast` and turn Fast mode off. Confirm `Fast` disappears from
the chat widget display.
3. Re-enter thread navigation via either path:
- Run `/side test`, then return to the main thread.
- Run `/agent`, enter a child thread, then return to the main thread.
4. Before this fix, `Fast` reappears in the main chat widget display
even though the opt-out was already persisted.
5. After this fix, `Fast` stays cleared.
## Verification
- `cargo test -p codex-tui
app::thread_session_state::tests::service_tier_sync_updates_active_cached_session
-- --exact`
## Why
Goal creation and completion are moving through the goal extension, but
the rest of Codex still observes goal state through `ThreadGoalUpdated`
events. Without an event from the extension-owned tool path, a
model-initiated `create_goal` or `update_goal` can mutate the backend
and return a tool result while app-server and TUI listeners miss the
goal state transition.
## What changed
- Added `GoalEventEmitter` as a small wrapper around the host
`ExtensionEventSink` to build `EventMsg::ThreadGoalUpdated` events for
goal updates.
- Threaded the registry event sink into `GoalExtension` and the
`GoalToolExecutor`s created by the extension. The public
`GoalExtension::new` constructor keeps a `NoopExtensionEventSink`
fallback for standalone use.
- Emitted a goal update after successful `create_goal` and `update_goal`
tool calls. Until `ToolCall` exposes the current turn submission id,
these events use the tool call id as the event id and leave `turn_id`
unset.
Relevant code:
-
[`GoalEventEmitter::thread_goal_updated`](1fe2d73890/codex-rs/ext/goal/src/events.rs (L19-L32))
- [`GoalToolExecutor` emission
points](1fe2d73890/codex-rs/ext/goal/src/tool.rs (L161-L190))
## Testing
- `cargo test -p codex-goal-extension`
## Why
Thread goal persistence is being prepared for a dedicated storage
boundary. Before that split, goal-specific reads, writes, accounting,
and cleanup were exposed directly on `StateRuntime`, so core and
app-server callsites stayed coupled to the full runtime instead of a
goal-specific store.
This PR introduces that boundary without changing the goal wire API or
current persistence behavior. Callers now go through
`StateRuntime::thread_goals()` and the new `GoalStore`, while
`GoalStore` still uses the existing state DB pool underneath.
## What changed
- Added `GoalStore` in `state/src/runtime/goals.rs` and exposed it from
`StateRuntime` via `thread_goals()`.
- Moved thread-goal reads, writes, status updates, pause, delete, and
usage accounting onto `GoalStore`.
- Updated core session goal handling, app-server goal RPCs, resume
snapshots, and goal tests to use the store boundary.
- Kept thread deletion responsible for cascading goal cleanup by
deleting the goal through the store only after a thread row is removed.
## Testing
- Existing goal persistence, resume, and accounting tests were updated
to exercise the new `GoalStore` access path.
## Why
Extensions can already expose typed contributions and receive host
capabilities such as `AgentSpawner`, but they do not have a typed way to
send protocol events back through the host. Extensions that need to
surface progress or status should not have to own persistence, ordering,
transport fanout, or logging decisions themselves.
## What
- Add `ExtensionEventSink`, a host-provided fire-and-forget sink for
`codex_protocol::protocol::Event`.
- Add `NoopExtensionEventSink` so hosts that do not expose extension
event emission keep the existing empty-registry behavior.
- Store the sink on `ExtensionRegistryBuilder` / `ExtensionRegistry`,
with `with_event_sink(...)` and `event_sink()` accessors, and re-export
the new capability from `codex-extension-api`.
## Testing
- Not run locally; PR metadata/body update only.
## Why
Extension lifecycle hooks sit on the host/extension boundary, but the
current trait surface only allows synchronous callbacks. That forces
extensions that need to seed, rehydrate, observe, or flush
extension-owned state during thread and turn transitions to either block
inside the callback or move async work into separate host plumbing.
This PR makes those lifecycle callbacks awaitable so extension
implementations can perform async work directly at the lifecycle point
where the host already has the relevant session, thread, or turn stores
available.
## What changed
- Makes `ThreadLifecycleContributor` and `TurnLifecycleContributor`
async in `codex-extension-api`.
- Awaits thread start/resume/stop and turn start/stop/abort lifecycle
callbacks from `codex-core`.
- Updates the guardian and memories extensions to implement the async
lifecycle trait surface.
- Updates the existing lifecycle tests to use async contributor
implementations.
- Adds `async-trait` to the crates that now expose or implement these
async object-safe lifecycle traits.
## Testing
- Existing `codex-core` lifecycle tests were updated to cover async
implementations for thread stop and turn abort ordering.
## Summary
- add app-server `plugin/installed` for mention-oriented plugin loading
- return installed plugins plus explicitly requested install-suggestion
rows
- keep remote handling on installed-state data instead of the broad
catalog listing path
## Why
The `@` mention surface only needs plugins that are usable now, plus a
small product-approved set of install suggestions. It does not need the
full catalog-shaped `plugin/list` payload that the Plugins page uses.
## Validation
- `just write-app-server-schema`
- `just fmt`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core-plugins`
- `cargo test -p codex-app-server --test all plugin_installed_`
## Notes
- The package-wide `cargo test -p codex-app-server` run still hits an
existing unrelated stack overflow in
`in_process::tests::in_process_start_clamps_zero_channel_capacity`.
- Companion webview PR: https://github.com/openai/openai/pull/915672
## Why
`memory_summary.md` is injected into every session, so its value depends
on staying compact, navigational, and easy to regenerate when the
expected shape changes. The previous consolidation prompt encouraged a
broad actionable inventory and allowed older summary structures to be
patched in place, which makes it easier for stale or overly verbose
summaries to keep accumulating.
This change makes the summary format explicitly versioned and biases
Phase 2 memory consolidation toward denser prompt-loaded context.
## What changed
- Require `memory_summary.md` to begin with an exact `v1` header.
- Teach consolidation to regenerate `memory_summary.md` from scratch
when the header is missing or incompatible, while still allowing
incremental updates to `MEMORY.md`.
- Tighten the `memory_summary.md` instructions so it acts as a compact
routing/index layer instead of a second handbook.
- Lower `MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT` from
`5_000` to `2_500` so the runtime prompt budget matches the denser
summary target.
## Verification
Not run; this is a prompt/template update plus a prompt budget constant
change.
## Why
`SandboxPolicy` is a legacy compatibility shape, but several core tests
still used it for ordinary turn setup even when the runtime path now
carries `PermissionProfile`. With the first cleanup PR merged, this
follow-up trims more core test scaffolding so remaining `SandboxPolicy`
matches are easier to classify as production compatibility,
legacy-boundary coverage, or explicit conversion tests.
## What Changed
- Updated apply-patch handler and runtime tests to pass
`PermissionProfile` directly.
- Changed sandboxing test helpers to build permission profiles without
first creating `SandboxPolicy` values.
- Converted request-permissions integration turns to pass
`PermissionProfile` through the test helper, leaving legacy sandbox
projection at the `Op::UserTurn` boundary.
- Converted unified exec integration helpers and direct turn submissions
to use `PermissionProfile` values instead of `SandboxPolicy` setup.
- Removed now-unused `SandboxPolicy` imports from the touched core
tests.
## Test Plan
- `just fmt`
- `cargo test -p codex-core --lib tools::sandboxing::tests`
- `cargo test -p codex-core --lib tools::runtimes::apply_patch::tests`
- `cargo test -p codex-core --lib tools::handlers::apply_patch::tests`
- `cargo test -p codex-core --lib unified_exec::process_manager::tests`
- `cargo test -p codex-core --test all request_permissions::`
- `cargo test -p codex-core --test all unified_exec::`
- `just fix -p codex-core`
## Summary
- Add `features.multi_agent_v2.tool_namespace` with config/schema
validation for Responses-compatible namespace values.
- Thread the resolved namespace into `ToolsConfig` for normal turns and
review turns.
- Wrap MultiAgentV2 tool specs and registry names in the configured
namespace when namespace tools are supported, while falling back to the
plain tool names when they are not.
## Validation
- `just fmt`
- `just write-config-schema`
- `cargo test -p codex-features multi_agent_v2_feature_config --
--nocapture`
- `cargo test -p codex-core test_build_specs_multi_agent_v2 --
--nocapture`
- `cargo test -p codex-core multi_agent_v2_config -- --nocapture`
- `cargo test -p codex-core
multi_agent_v2_rejects_invalid_tool_namespace -- --nocapture`
- `cargo test -p codex-tools`
- `git diff --check`
## Why
`TurnHandle.run()` returned the raw app-server `Turn`, whose live
start/completed payloads do not include loaded `items`, so users saw
empty `items` after starting a turn. That made the handle-based path
behave differently from `Thread.run(...)`, and pushed examples toward
persisted-thread reads plus helper extraction.
This PR makes the run APIs standalone: starting a turn and running it
returns collected turn data directly, or fails visibly when required
stream events are missing.
## What Changed
- Replaces the public `RunResult` export with `TurnResult`.
- Adds turn metadata to `TurnResult`: `id`, `status`, `error`,
`started_at`, `completed_at`, and `duration_ms`, alongside
`final_response`, `items`, and `usage`.
- Changes `TurnHandle.run()` and `AsyncTurnHandle.run()` to consume
stream events with the same collector used by `Thread.run(...)`.
- Exports `TurnError` from `openai_codex.types` for the new result
shape.
- Updates tests, examples, docs, and the walkthrough notebook to use
`result.final_response` and `result.items` directly.
- Removes persisted-thread helper paths and placeholder/skipped control
flows from the public examples and notebook.
## Verification
- `python3 -m py_compile ...` over changed SDK, example, and test Python
files.
- `python3 -c "import json;
json.load(open('sdk/python/notebooks/sdk_walkthrough.ipynb'))"`
- `git diff --check`
- `PYTHONPATH=sdk/python/src python3 -c ...` import/signature smoke for
`TurnResult`, `TurnHandle.run`, and `AsyncTurnHandle.run`.
## Why
The Python SDK can already create threads and run turns, but
authentication still has to be arranged outside the SDK. App-server
already exposes account login, account inspection, logout, and
`account/login/completed` notifications, so SDK users currently have to
work around a missing public client layer for a core setup step.
This change makes authentication a normal SDK workflow while preserving
the backend flow shape: API-key login completes immediately, and
interactive ChatGPT flows return live handles that complete later
through app-server notifications.
## What changed
- Added public sync and async auth methods on `Codex` / `AsyncCodex`:
- `login_api_key(...)`
- `login_chatgpt()`
- `login_chatgpt_device_code()`
- `account(...)`
- `logout()`
- Added public browser-login and device-code handle types with
attempt-local `wait()` and `cancel()` helpers. Cancellation stays on the
handle instead of a root-level SDK method.
- Extended the Python app-server client and notification router so login
completion events are routed by `login_id` without consuming unrelated
global notifications.
- Kept login request/handle logic in a focused internal `_login.py`
module so `api.py` remains the public facade instead of absorbing more
auth plumbing.
- Exported the new handle types plus curated account/login response
types from the SDK surfaces.
- Updated SDK docs, added sync/async login walkthrough examples, and
added a notebook login walkthrough cell.
## Verification
Added SDK coverage for:
- API-key login, account readback, and logout through the app-server
harness in both sync and async clients.
- Browser login cancellation plus `handle.wait()` completion through the
real app-server boundary used by the Python SDK harness.
- Waiter routing that stays scoped across replaced interactive login
attempts, plus async handle cancellation coverage.
- Login notification demuxing, replay of early completion events, and
async client delegation.
- Public export/signature assertions.
- Real integration-suite smoke coverage for the new examples and
notebook login cell.
## Why
The TUI can run against a remote app server, but several high-traffic
settings still persisted by editing the local config file. That sends
remote sessions' preference writes to the wrong machine and lets local
disk state drift from the app-server-owned config.
This is **[1 of 4]** in a stacked series that moves TUI-owned config
mutations onto app-server APIs.
## What changed
- Added a small TUI helper for typed app-server config writes.
- Routed primary interactive preference writes through
`config/batchWrite`.
- Preserved existing profile scoping for settings that already support
`profiles.<profile>.*` overrides.
## Config keys affected
- `model`
- `model_reasoning_effort`
- `personality`
- `service_tier`
- `plan_mode_reasoning_effort`
- `approvals_reviewer`
- `notice.fast_default_opt_out`
- Profile-scoped equivalents under `profiles.<profile>.*`
## Suggested manual validation
- Connect the TUI to a remote app server, change `model` and
`model_reasoning_effort`, reconnect, and confirm the remote config
retained both values while the local `config.toml` did not change.
- Change `personality`, `plan_mode_reasoning_effort`, and the explicit
auto-review selection, then reconnect and confirm those choices persist
through the app server.
- Clear the service tier back to default and confirm `service_tier` is
cleared while `notice.fast_default_opt_out = true` is persisted
remotely.
- Repeat one setting change with an active profile and confirm the write
lands under `profiles.<profile>.*`.
## Stack
1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
primary settings writes
2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
and skill enablement
3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
feature and memory toggles
4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
startup and onboarding bookkeeping
## Why
The `spawn_agent` model override guidance is uncapped and bloating
context. We need to trim down each entry and cap total entries.
picked 5 as cap, we can change
## What changed
- Cap the model override summaries shown in `spawn_agent` to the first 5
picker-visible models, preserving the existing priority ordering from
the models manager.
- Condense each rendered entry to the actionable pieces the model needs:
- use the model slug as the label
- render compact reasoning effort lists with the default marked inline
- render only service tier IDs, and omit the clause when no tiers are
available
- Update coverage so the compact formatter shape and the top-5 cap are
exercised, and keep the end-to-end request assertion aligned with real
model metadata.
## Example
Before:
`- gpt-5.4 ('gpt-5.4\'): Strong model for everyday coding. Default
reasoning effort: medium. Supported reasoning efforts: low (Fast
responses with lighter reasoning), medium (Balances speed and reasoning
depth for everyday tasks), high (Greater reasoning depth for complex
problems), xhigh (Extra high reasoning depth for complex problems).
Supported service tiers: priority (Fast: 1.5x speed, increased usage).`
After:
`- 'gpt-5.4': Strong model for everyday coding. Reasoning efforts: low,
medium (default), high, xhigh. Service tiers: priority.`
This updates remote `exec-server` registration to use normal Codex auth
instead of a registry-issued credential. The registry request is built
from the existing auth-provider path, which preserves the biscuit-only
registry contract introduced in
[openai/openai#924101](https://github.com/openai/openai/pull/924101)
while removing the old remote registry bearer env var and its direct
transport assumptions.
The default remote flow uses persisted ChatGPT auth from the normal
Codex config/storage path. This PR also includes the containerized Agent
Identity path needed by
[openai/openai#924260](https://github.com/openai/openai/pull/924260):
remote `exec-server` accepts `--allow-agent-identity-auth`, permits
Agent Identity auth loaded from `CODEX_ACCESS_TOKEN` only when that flag
is present, and reuses the existing Agent task registration plus derived
`AgentAssertion` header generation. API-key auth remains unsupported,
and Agent Identity stays opt-in.
Validation performed beyond normal presubmit coverage:
- `cargo fmt --all --check`
- `cargo check -p codex-cli`
- `cargo test -p codex-exec-server`
- `cargo test -p codex-cli exec_server_agent_identity_auth_flag_`
- `cargo test -p codex-cli remote_exec_server_auth_mode_`
I also attempted `cargo test -p codex-cli`. The new CLI tests passed
inside that run, but the suite ended on an unrelated local
marketplace-state failure in
`plugin_list_excludes_unconfigured_repo_local_marketplaces`.
## Why
`SandboxPolicy` is now a legacy compatibility shape, but several tests
still built a `SandboxPolicy` only to immediately convert it into
`PermissionProfile` for APIs that already accept canonical runtime
permissions. Those detours make it harder to audit where legacy sandbox
policy is still required, because boundary-only usages are mixed
together with ordinary test setup.
## What Changed
- Updated tests in `codex-core`, `codex-exec`, `codex-analytics`, and
`codex-config` to construct `PermissionProfile` values directly when the
code under test takes a permission profile.
- Changed exec-policy, request-permissions, session, and sandbox test
helpers to pass `PermissionProfile` through instead of converting from
`SandboxPolicy` internally.
- Left `SandboxPolicy` in place where tests are explicitly exercising
legacy compatibility or request/response boundaries.
## Test Plan
- `cargo test -p codex-analytics -p codex-config`
- `cargo test -p codex-core --lib safety::tests`
- `cargo test -p codex-core --lib exec_policy::tests::`
- `cargo test -p codex-core --lib exec::tests`
- `cargo test -p codex-core --lib guardian_review_session_config`
- `cargo test -p codex-core --lib tools::network_approval::tests`
- `cargo test -p codex-core --lib
tools::runtimes::shell::unix_escalation::tests`
- `cargo test -p codex-core --lib managed_network`
- `cargo test -p codex-core --test all request_permissions::`
- `cargo test -p codex-exec sandbox`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23030).
* #23036
* __->__ #23030
## Why
Goal completion follow-up turns currently receive a preformatted English
usage sentence such as `time used: 2586 seconds`. That nudges the model
to echo an awkward raw seconds count in the final reply, even though the
tool result already exposes structured usage fields like
`goal.timeUsedSeconds`, `goal.tokensUsed`, and `goal.tokenBudget`.
## What changed
- Replace the preformatted completion usage sentence with guidance to
read the structured goal fields from the tool result.
- Preserve token-budget reporting while allowing the model to phrase
elapsed time in a concise, human-friendly way that fits the response
language.
- Update core coverage for both the generated completion guidance and
the session flow that forwards it back to the model.
## Verification
Previously, it would have output a final message indicating that it
"worked for 303 seconds". Now it shows the following:
<img width="286" height="35" alt="image"
src="https://github.com/user-attachments/assets/d7011880-9449-46a7-856f-4e50ae00eb45"
/>
2026-05-16 11:49:40 -07:00
933 changed files with 55069 additions and 20512 deletions
description: Update Codex's pinned `v8` / `rusty_v8` versions, validate the release-candidate path, and investigate failed V8 canary or artifact builds. Use when asked to bump V8, update `rusty_v8` artifacts, prepare or validate a V8 release candidate, check `v8-canary`, or diagnose why a V8 version update no longer builds.
---
# Update V8 Version
## Core Workflow
1. Read `third_party/v8/README.md` and follow its version-bump sequence. Treat
that document as the release-process source of truth.
2. Inspect and update the concrete repo surfaces that carry the pin:
-`codex-rs/Cargo.toml`
-`codex-rs/Cargo.lock`
-`MODULE.bazel`
-`third_party/v8/BUILD.bazel`
-`third_party/v8/README.md`
- the matching `third_party/v8/rusty_v8_<version>.sha256` manifest when the
remaining prebuilt inputs change
3. Keep the existing checksum helpers in the loop:
"description":"A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type":"string"
},
"ActivePermissionProfile":{
"properties":{
"extends":{
"default":null,
"description":"Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
"type":[
"string",
"null"
]
},
"id":{
"description":"Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type":"string"
}
},
"required":[
"id"
],
"type":"object"
},
"AddCreditsNudgeCreditType":{
"enum":[
"credits",
@@ -611,6 +591,13 @@
"integer",
"null"
]
},
"threadId":{
"description":"Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"description":"Opaque pagination cursor returned by a previous call.",
"type":[
"string",
"null"
]
},
"cwd":{
"description":"Optional working directory to resolve project config layers.",
"type":[
"string",
"null"
]
},
"limit":{
"description":"Optional page size; defaults to the full result set.",
"format":"uint32",
"minimum":0.0,
"type":[
"integer",
"null"
]
}
},
"type":"object"
},
"Personality":{
"enum":[
"none",
@@ -1589,9 +1624,35 @@
],
"type":"object"
},
"PluginInstalledParams":{
"properties":{
"cwds":{
"description":"Optional working directories used to discover repo marketplaces.",
"items":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":[
"array",
"null"
]
},
"installSuggestionPluginNames":{
"description":"Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"items":{
"type":"string"
},
"type":[
"array",
"null"
]
}
},
"type":"object"
},
"PluginListMarketplaceKind":{
"enum":[
"local",
"vertical",
"workspace-directory",
"shared-with-me"
],
@@ -2995,7 +3056,7 @@
"type":"object"
},
"ThreadForkParams":{
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"properties":{
"approvalPolicy":{
"anyOf":[
@@ -3095,10 +3156,68 @@
],
"type":"object"
},
"ThreadGoalClearParams":{
"properties":{
"threadId":{
"type":"string"
}
},
"required":[
"threadId"
],
"type":"object"
},
"ThreadGoalGetParams":{
"properties":{
"threadId":{
"type":"string"
}
},
"required":[
"threadId"
],
"type":"object"
},
"ThreadGoalSetParams":{
"properties":{
"objective":{
"type":[
"string",
"null"
]
},
"status":{
"anyOf":[
{
"$ref":"#/definitions/ThreadGoalStatus"
},
{
"type":"null"
}
]
},
"threadId":{
"type":"string"
},
"tokenBudget":{
"format":"int64",
"type":[
"integer",
"null"
]
}
},
"required":[
"threadId"
],
"type":"object"
},
"ThreadGoalStatus":{
"enum":[
"active",
"paused",
"blocked",
"usageLimited",
"budgetLimited",
"complete"
],
@@ -3399,7 +3518,7 @@
]
},
"ThreadResumeParams":{
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"Parent profile identifier from the selected permissions profile's `extends` setting, when present.",
"type":[
"string",
"null"
]
},
"id":{
"description":"Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type":"string"
}
},
"required":[
"id"
],
"type":"object"
},
"AdditionalFileSystemPermissions":{
"properties":{
"entries":{
@@ -415,6 +435,65 @@
],
"type":"object"
},
"ApprovalsReviewer":{
"description":"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum":[
"user",
"auto_review",
"guardian_subagent"
],
"type":"string"
},
"AskForApproval":{
"oneOf":[
{
"enum":[
"untrusted",
"on-failure",
"on-request",
"never"
],
"type":"string"
},
{
"additionalProperties":false,
"properties":{
"granular":{
"properties":{
"mcp_elicitations":{
"type":"boolean"
},
"request_permissions":{
"default":false,
"type":"boolean"
},
"rules":{
"type":"boolean"
},
"sandbox_approval":{
"type":"boolean"
},
"skill_approval":{
"default":false,
"type":"boolean"
}
},
"required":[
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type":"object"
}
},
"required":[
"granular"
],
"title":"GranularAskForApproval",
"type":"object"
}
]
},
"AuthMode":{
"description":"Authentication mode for OpenAI-backed providers.",
"oneOf":[
@@ -658,6 +737,22 @@
],
"type":"string"
},
"CollaborationMode":{
"description":"Collaboration mode for a Codex session.",
"properties":{
"mode":{
"$ref":"#/definitions/ModeKind"
},
"settings":{
"$ref":"#/definitions/Settings"
}
},
"required":[
"mode",
"settings"
],
"type":"object"
},
"CommandAction":{
"oneOf":[
{
@@ -1085,7 +1180,7 @@
"enum":[
"read",
"write",
"none"
"deny"
],
"type":"string"
},
@@ -1740,6 +1835,8 @@
"postCompact",
"sessionStart",
"userPromptSubmit",
"subagentStart",
"subagentStop",
"stop"
],
"type":"string"
@@ -2257,6 +2354,14 @@
}
]
},
"ModeKind":{
"description":"Initial collaboration mode to use when the TUI starts.",
"enum":[
"plan",
"default"
],
"type":"string"
},
"ModelRerouteReason":{
"enum":[
"highRiskCyberActivity"
@@ -2318,6 +2423,13 @@
],
"type":"object"
},
"NetworkAccess":{
"enum":[
"restricted",
"enabled"
],
"type":"string"
},
"NetworkApprovalProtocol":{
"enum":[
"http",
@@ -2401,6 +2513,14 @@
}
]
},
"Personality":{
"enum":[
"none",
"friendly",
"pragmatic"
],
"type":"string"
},
"PlanDeltaNotification":{
"description":"EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.",
"properties":{
@@ -2654,6 +2774,26 @@
],
"type":"string"
},
"ReasoningSummary":{
"description":"A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries",
"oneOf":[
{
"enum":[
"auto",
"concise",
"detailed"
],
"type":"string"
},
{
"description":"Option to disable reasoning summaries.",
"enum":[
"none"
],
"type":"string"
}
]
},
"ReasoningSummaryPartAddedNotification":{
"properties":{
"itemId":{
@@ -2806,6 +2946,105 @@
},
"type":"object"
},
"SandboxPolicy":{
"oneOf":[
{
"properties":{
"type":{
"enum":[
"dangerFullAccess"
],
"title":"DangerFullAccessSandboxPolicyType",
"type":"string"
}
},
"required":[
"type"
],
"title":"DangerFullAccessSandboxPolicy",
"type":"object"
},
{
"properties":{
"networkAccess":{
"default":false,
"type":"boolean"
},
"type":{
"enum":[
"readOnly"
],
"title":"ReadOnlySandboxPolicyType",
"type":"string"
}
},
"required":[
"type"
],
"title":"ReadOnlySandboxPolicy",
"type":"object"
},
{
"properties":{
"networkAccess":{
"allOf":[
{
"$ref":"#/definitions/NetworkAccess"
}
],
"default":"restricted"
},
"type":{
"enum":[
"externalSandbox"
],
"title":"ExternalSandboxSandboxPolicyType",
"type":"string"
}
},
"required":[
"type"
],
"title":"ExternalSandboxSandboxPolicy",
"type":"object"
},
{
"properties":{
"excludeSlashTmp":{
"default":false,
"type":"boolean"
},
"excludeTmpdirEnvVar":{
"default":false,
"type":"boolean"
},
"networkAccess":{
"default":false,
"type":"boolean"
},
"type":{
"enum":[
"workspaceWrite"
],
"title":"WorkspaceWriteSandboxPolicyType",
"type":"string"
},
"writableRoots":{
"default":[],
"items":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":"array"
}
},
"required":[
"type"
],
"title":"WorkspaceWriteSandboxPolicy",
"type":"object"
}
]
},
"ServerRequestResolvedNotification":{
"properties":{
"requestId":{
@@ -2861,6 +3100,34 @@
}
]
},
"Settings":{
"description":"Settings for a collaboration mode.",
"properties":{
"developer_instructions":{
"type":[
"string",
"null"
]
},
"model":{
"type":"string"
},
"reasoning_effort":{
"anyOf":[
{
"$ref":"#/definitions/ReasoningEffort"
},
{
"type":"null"
}
]
}
},
"required":[
"model"
],
"type":"object"
},
"SkillsChangedNotification":{
"description":"Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.",
"description":"Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"description":"Optional working directories used to discover repo marketplaces.",
"items":{
"$ref":"#/definitions/v2/AbsolutePathBuf"
},
"type":[
"array",
"null"
]
},
"installSuggestionPluginNames":{
"description":"Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"description":"Optional working directories used to discover repo marketplaces.",
"items":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":[
"array",
"null"
]
},
"installSuggestionPluginNames":{
"description":"Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type":"string"
},
"ActivePermissionProfile":{
"properties":{
"extends":{
"default":null,
"description":"Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
"type":[
"string",
"null"
]
},
"id":{
"description":"Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type":"string"
}
},
"required":[
"id"
],
"type":"object"
},
"CommandExecTerminalSize":{
"description":"PTY size in character cells for `command/exec` PTY sessions.",
"description":"Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.",
"description":"A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type":"string"
}
},
"properties":{
"cwds":{
"description":"Optional working directories used to discover repo marketplaces.",
"items":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":[
"array",
"null"
]
},
"installSuggestionPluginNames":{
"description":"Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.",
"description":"A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type":"string"
},
"MarketplaceInterface":{
"properties":{
"displayName":{
"type":[
"string",
"null"
]
}
},
"type":"object"
},
"MarketplaceLoadErrorInfo":{
"properties":{
"marketplacePath":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"message":{
"type":"string"
}
},
"required":[
"marketplacePath",
"message"
],
"type":"object"
},
"PluginAuthPolicy":{
"enum":[
"ON_INSTALL",
"ON_USE"
],
"type":"string"
},
"PluginAvailability":{
"oneOf":[
{
"enum":[
"DISABLED_BY_ADMIN"
],
"type":"string"
},
{
"description":"Plugin-service currently sends `\"ENABLED\"` for available remote plugins. Codex app-server exposes `\"AVAILABLE\"` in its API; the alias keeps decoding compatible with that upstream response.",
"enum":[
"AVAILABLE"
],
"type":"string"
}
]
},
"PluginInstallPolicy":{
"enum":[
"NOT_AVAILABLE",
"AVAILABLE",
"INSTALLED_BY_DEFAULT"
],
"type":"string"
},
"PluginInterface":{
"properties":{
"brandColor":{
"type":[
"string",
"null"
]
},
"capabilities":{
"items":{
"type":"string"
},
"type":"array"
},
"category":{
"type":[
"string",
"null"
]
},
"composerIcon":{
"anyOf":[
{
"$ref":"#/definitions/AbsolutePathBuf"
},
{
"type":"null"
}
],
"description":"Local composer icon path, resolved from the installed plugin package."
},
"composerIconUrl":{
"description":"Remote composer icon URL from the plugin catalog.",
"type":[
"string",
"null"
]
},
"defaultPrompt":{
"description":"Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.",
"items":{
"type":"string"
},
"type":[
"array",
"null"
]
},
"developerName":{
"type":[
"string",
"null"
]
},
"displayName":{
"type":[
"string",
"null"
]
},
"logo":{
"anyOf":[
{
"$ref":"#/definitions/AbsolutePathBuf"
},
{
"type":"null"
}
],
"description":"Local logo path, resolved from the installed plugin package."
},
"logoUrl":{
"description":"Remote logo URL from the plugin catalog.",
"type":[
"string",
"null"
]
},
"longDescription":{
"type":[
"string",
"null"
]
},
"privacyPolicyUrl":{
"type":[
"string",
"null"
]
},
"screenshotUrls":{
"description":"Remote screenshot URLs from the plugin catalog.",
"items":{
"type":"string"
},
"type":"array"
},
"screenshots":{
"description":"Local screenshot paths, resolved from the installed plugin package.",
"items":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":"array"
},
"shortDescription":{
"type":[
"string",
"null"
]
},
"termsOfServiceUrl":{
"type":[
"string",
"null"
]
},
"websiteUrl":{
"type":[
"string",
"null"
]
}
},
"required":[
"capabilities",
"screenshotUrls",
"screenshots"
],
"type":"object"
},
"PluginMarketplaceEntry":{
"properties":{
"interface":{
"anyOf":[
{
"$ref":"#/definitions/MarketplaceInterface"
},
{
"type":"null"
}
]
},
"name":{
"type":"string"
},
"path":{
"anyOf":[
{
"$ref":"#/definitions/AbsolutePathBuf"
},
{
"type":"null"
}
],
"description":"Local marketplace file path when the marketplace is backed by a local file. Remote-only catalog marketplaces do not have a local path."
},
"plugins":{
"items":{
"$ref":"#/definitions/PluginSummary"
},
"type":"array"
}
},
"required":[
"name",
"plugins"
],
"type":"object"
},
"PluginShareContext":{
"properties":{
"creatorAccountUserId":{
"type":[
"string",
"null"
]
},
"creatorName":{
"type":[
"string",
"null"
]
},
"discoverability":{
"anyOf":[
{
"$ref":"#/definitions/PluginShareDiscoverability"
},
{
"type":"null"
}
]
},
"remotePluginId":{
"type":"string"
},
"remoteVersion":{
"default":null,
"description":"Version of the remote shared plugin release when available.",
"type":[
"string",
"null"
]
},
"sharePrincipals":{
"items":{
"$ref":"#/definitions/PluginSharePrincipal"
},
"type":[
"array",
"null"
]
},
"shareUrl":{
"type":[
"string",
"null"
]
}
},
"required":[
"remotePluginId"
],
"type":"object"
},
"PluginShareDiscoverability":{
"enum":[
"LISTED",
"UNLISTED",
"PRIVATE"
],
"type":"string"
},
"PluginSharePrincipal":{
"properties":{
"name":{
"type":"string"
},
"principalId":{
"type":"string"
},
"principalType":{
"$ref":"#/definitions/PluginSharePrincipalType"
},
"role":{
"$ref":"#/definitions/PluginSharePrincipalRole"
}
},
"required":[
"name",
"principalId",
"principalType",
"role"
],
"type":"object"
},
"PluginSharePrincipalRole":{
"enum":[
"reader",
"editor",
"owner"
],
"type":"string"
},
"PluginSharePrincipalType":{
"enum":[
"user",
"group",
"workspace"
],
"type":"string"
},
"PluginSource":{
"oneOf":[
{
"properties":{
"path":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":{
"enum":[
"local"
],
"title":"LocalPluginSourceType",
"type":"string"
}
},
"required":[
"path",
"type"
],
"title":"LocalPluginSource",
"type":"object"
},
{
"properties":{
"path":{
"type":[
"string",
"null"
]
},
"refName":{
"type":[
"string",
"null"
]
},
"sha":{
"type":[
"string",
"null"
]
},
"type":{
"enum":[
"git"
],
"title":"GitPluginSourceType",
"type":"string"
},
"url":{
"type":"string"
}
},
"required":[
"type",
"url"
],
"title":"GitPluginSource",
"type":"object"
},
{
"description":"The plugin is available in the remote catalog. Download metadata is kept server-side and is not exposed through the app-server API.",
"properties":{
"type":{
"enum":[
"remote"
],
"title":"RemotePluginSourceType",
"type":"string"
}
},
"required":[
"type"
],
"title":"RemotePluginSource",
"type":"object"
}
]
},
"PluginSummary":{
"properties":{
"authPolicy":{
"$ref":"#/definitions/PluginAuthPolicy"
},
"availability":{
"allOf":[
{
"$ref":"#/definitions/PluginAvailability"
}
],
"default":"AVAILABLE",
"description":"Availability state for installing and using the plugin."
},
"enabled":{
"type":"boolean"
},
"id":{
"type":"string"
},
"installPolicy":{
"$ref":"#/definitions/PluginInstallPolicy"
},
"installed":{
"type":"boolean"
},
"interface":{
"anyOf":[
{
"$ref":"#/definitions/PluginInterface"
},
{
"type":"null"
}
]
},
"keywords":{
"default":[],
"items":{
"type":"string"
},
"type":"array"
},
"localVersion":{
"default":null,
"description":"Version of the locally materialized plugin package when available.",
"type":[
"string",
"null"
]
},
"name":{
"type":"string"
},
"remotePluginId":{
"description":"Backend remote plugin identifier when available.",
"type":[
"string",
"null"
]
},
"shareContext":{
"anyOf":[
{
"$ref":"#/definitions/PluginShareContext"
},
{
"type":"null"
}
],
"description":"Remote sharing context associated with this plugin when available."
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nThe precedence is: history > path > thread_id. If using history or path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"description":"There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.",
"description":"A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type":"string"
},
"ActivePermissionProfile":{
"properties":{
"extends":{
"default":null,
"description":"Parent profile identifier from the selected permissions profile's `extends` setting, when present.",
"type":[
"string",
"null"
]
},
"id":{
"description":"Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
"type":"string"
}
},
"required":[
"id"
],
"type":"object"
},
"ApprovalsReviewer":{
"description":"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum":[
"user",
"auto_review",
"guardian_subagent"
],
"type":"string"
},
"AskForApproval":{
"oneOf":[
{
"enum":[
"untrusted",
"on-failure",
"on-request",
"never"
],
"type":"string"
},
{
"additionalProperties":false,
"properties":{
"granular":{
"properties":{
"mcp_elicitations":{
"type":"boolean"
},
"request_permissions":{
"default":false,
"type":"boolean"
},
"rules":{
"type":"boolean"
},
"sandbox_approval":{
"type":"boolean"
},
"skill_approval":{
"default":false,
"type":"boolean"
}
},
"required":[
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type":"object"
}
},
"required":[
"granular"
],
"title":"GranularAskForApproval",
"type":"object"
}
]
},
"CollaborationMode":{
"description":"Collaboration mode for a Codex session.",
"properties":{
"mode":{
"$ref":"#/definitions/ModeKind"
},
"settings":{
"$ref":"#/definitions/Settings"
}
},
"required":[
"mode",
"settings"
],
"type":"object"
},
"ModeKind":{
"description":"Initial collaboration mode to use when the TUI starts.",
"description":"A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries",
"oneOf":[
{
"enum":[
"auto",
"concise",
"detailed"
],
"type":"string"
},
{
"description":"Option to disable reasoning summaries.",
"enum":[
"none"
],
"type":"string"
}
]
},
"SandboxPolicy":{
"oneOf":[
{
"properties":{
"type":{
"enum":[
"dangerFullAccess"
],
"title":"DangerFullAccessSandboxPolicyType",
"type":"string"
}
},
"required":[
"type"
],
"title":"DangerFullAccessSandboxPolicy",
"type":"object"
},
{
"properties":{
"networkAccess":{
"default":false,
"type":"boolean"
},
"type":{
"enum":[
"readOnly"
],
"title":"ReadOnlySandboxPolicyType",
"type":"string"
}
},
"required":[
"type"
],
"title":"ReadOnlySandboxPolicy",
"type":"object"
},
{
"properties":{
"networkAccess":{
"allOf":[
{
"$ref":"#/definitions/NetworkAccess"
}
],
"default":"restricted"
},
"type":{
"enum":[
"externalSandbox"
],
"title":"ExternalSandboxSandboxPolicyType",
"type":"string"
}
},
"required":[
"type"
],
"title":"ExternalSandboxSandboxPolicy",
"type":"object"
},
{
"properties":{
"excludeSlashTmp":{
"default":false,
"type":"boolean"
},
"excludeTmpdirEnvVar":{
"default":false,
"type":"boolean"
},
"networkAccess":{
"default":false,
"type":"boolean"
},
"type":{
"enum":[
"workspaceWrite"
],
"title":"WorkspaceWriteSandboxPolicyType",
"type":"string"
},
"writableRoots":{
"default":[],
"items":{
"$ref":"#/definitions/AbsolutePathBuf"
},
"type":"array"
}
},
"required":[
"type"
],
"title":"WorkspaceWriteSandboxPolicy",
"type":"object"
}
]
},
"Settings":{
"description":"Settings for a collaboration mode.",
* Optional page size; defaults to a reasonable server-side value.
*/
limit?: number|null,};
limit?: number|null,
/**
* Optional loaded thread id. Pass this when showing feature state for an
* existing thread so enablement is computed from that thread's refreshed
* config, including project-local config for the thread's cwd.
*/
threadId?: string|null,};
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.