## Why
The standalone `codex-app-server` binary already routed a
`CliConfigOverrides` value into app-server startup, but its own clap
args did not expose the shared `-c/--config` option. That meant
`codex-app-server -c key=value` was rejected before the existing config
override path could run, unlike the main `codex` CLI.
## What Changed
- Flatten `CliConfigOverrides` into `AppServerArgs` in
`codex-rs/app-server/src/main.rs`.
- Pass parsed overrides to `run_main_with_transport_options` instead of
always using `CliConfigOverrides::default()`.
- Add a binary parser test covering both `-c` and `--config` for the
standalone app-server.
## Verification
- `just test -p codex-app-server
app_server_accepts_cli_config_overrides`
The broader `just test -p codex-app-server` run was also attempted. It
compiled and ran 812 tests, with 796 passing, but failed in this local
sandbox on unrelated `sandbox-exec: sandbox_apply: Operation not
permitted` command-exec/turn integration paths and a skills watcher
timeout.
## Why
Standalone image generation returns image bytes to the model, but the
model also needs the host artifact path to reference the generated file
in follow-up work.
## What changed
- Append the default saved-image path hint alongside the generated image
tool output.
- Reuse the existing core image-generation hint text.
- Pass the thread ID and Codex home directory needed to compute the
artifact path.
- Add app-server and extension coverage for the model-visible hint.
## Validation
- `just fmt`
- `just bazel-lock-check`
- `just test -p codex-app-server
standalone_image_generation_returns_saved_path_hint_to_model`
## Summary
`hooks/list` only consumes plugin hook declarations, but previously
loaded every enabled plugin's skills, MCP configuration, apps, and
capability summary before discarding them.
In a local benchmark, this reduced `hooks/list` latency by over 100ms
(e.g., from 594 to 467ms on startup, and 168 to 16ms when making a
`hooks/list` call later in the same TUI session). This is on the
critical path to rendering the TUI, so every 10s of ms should be eyed
skeptically (IMO).
This change adds a hook-specific plugin loading path that preserves
plugin enablement, remote/local conflict resolution, deterministic
ordering, manifest resolution, and hook-loading warnings while skipping
unrelated capabilities. (I think there's room for a more general design
here that allows you to project the capabilities you need at load-time,
but that seems unnecessary right now.)
## Summary
Restore Windows coverage for standalone image generation in code mode.
The previous test executed a V8-backed code-mode cell on Windows CI,
where that runtime path is intentionally excluded because it is
unreliable. The test was then ignored entirely on Windows, removing
useful coverage.
This splits the test into two checks:
- All platforms verify that `image_gen__imagegen` is exposed to the
model when image generation is configured for code mode only.
- Non-Windows platforms continue to execute the full V8-backed flow and
verify that the nested image-generation call succeeds.
## Verification
- `just fmt`
- `git diff --check`
- `just test -p codex-app-server standalone_image_generation`
Result: 3 tests passed, plus the required bench smoke check.
Fixes#25950.
## Why
Forking a renamed thread could fall back to the source thread's
first-prompt title because the fork path did not preserve the source's
explicit name. That meant fork-of-renamed-fork flows could show stale
sidebar labels even though the user had renamed the parent.
## What changed
`thread/fork` now reads the source thread's distinct `name`, normalizes
it, persists it onto materialized forks, and applies it to the returned
API thread. Because the source `name` already excludes first-prompt
pseudo-titles, forks inherit only an explicit user rename instead of
stale generated metadata.
## Summary
- Read `default_prompts` from remote plugin release metadata.
- Prefer the plural prompt list over legacy `default_prompt`.
- Fall back to `default_prompt` as a single-item list for backward
compatibility.
## Testing
- `just test -p codex-core-plugins`
- `just test -p codex-app-server`
## Why
#25450 attempts a broad `SandboxPolicy` removal across several unrelated
surfaces, which makes it hard to review and still leaves new helper code
moving legacy policies around. This PR is a narrower alternative:
migrate only the exec-side Windows sandbox plumbing so the review can
focus on one production path and one compatibility boundary.
The goal is to stop threading `SandboxPolicy` through exec code without
expanding the migration into app-server, protocol, telemetry, config, or
session behavior.
## What changed
- Removed `ExecRequest::compatibility_sandbox_policy()`.
- Changed the Windows restricted-token and elevated filesystem override
helpers to accept `PermissionProfile` plus the split filesystem/network
policies instead of a `SandboxPolicy`.
- Kept the remaining legacy projection local to the writable-root
comparison that still needs to compare split policy behavior against the
legacy Windows backend model.
- Rejected restricted split filesystem policies that still grant
full-disk writes before using the Windows restricted-token backend,
preserving the previous clear-failure behavior for profiles that project
to `ExternalSandbox`.
- Updated the Windows sandbox override tests to exercise the new call
shape and cover the full-write split-profile regression.
## Verification
- `just test -p codex-core windows_restricted_token`
- `just test -p codex-core windows_elevated`
## Why
Remote-control clients need to list and revoke controller-device grants
without enabling or enrolling the local relay. These are signed-in
account-management operations, so coupling them to websocket, pairing,
enrollment, or persisted relay state would prevent clients from managing
stale grants from the picker.
Related enhancement request: N/A. This adds the Codex app-server surface
for the planned upstream environment-scoped revoke endpoint.
## What Changed
- Added experimental app-server v2 RPCs:
- `remoteControl/client/list`
- `remoteControl/client/revoke`
- Added picker-oriented protocol types and standard generated schema
fixtures. The list response intentionally omits backend account id,
enrollment status, and location fields.
- Added `app-server-transport/src/transport/remote_control/clients.rs`
for environment-scoped GET and DELETE requests. It builds escaped URL
path segments, forwards optional pagination query fields, sends ChatGPT
auth plus `chatgpt-account-id`, converts RFC3339 `last_seen_at` values
to Unix seconds, accepts `204 No Content` revoke responses, and retries
once after a `401`.
- Extracted shared ChatGPT auth loading and recovery into
`app-server-transport/src/transport/remote_control/auth.rs` so
websocket, pairing, and client management use the same account-auth
boundary.
- Retained the configured remote-control base URL on
`RemoteControlHandle` and resolve management URLs lazily, preserving
deferred validation while relay startup is disabled.
- Registered list as `global_shared_read("remote-control-clients")` and
revoke as `global("remote-control-clients")`.
## Verification
- Added transport coverage proving list and revoke work while relay
state is disabled, IDs are escaped, picker-only fields are returned,
timestamps are converted, revoke accepts `204`, auth headers are
forwarded, `401` retries exactly once, `403` is not retried, and
malformed list payloads retain decode context.
- Added an app-server integration test proving both JSON-RPC methods
work before relay enablement and successful revoke returns `{}`.
- Regenerated and validated experimental and standard app-server schema
fixtures.
## Why
Standalone image generation remained top-level-only in code-mode
sessions.
## What changed
- Change imagegen exposure from `DirectModelOnly` to `Direct`.
- Keep direct-mode access while enabling nested code-mode access.
- Add a focused regression test for the exposure contract.
## Validation
- `just test -p codex-image-generation-extension`
## Summary
- update the app-server image generation integration test to use
`TestAppServer`
- completes the test helper rename from #25701 for this newer test file
## Validation
- `cargo fmt -- --config imports_granularity=Item`
- `cargo check -p codex-app-server --test all`
Note: `just fmt` ran Rust formatting but failed on Python/SDK formatting
because the sandbox could not access the local `uv` cache.
## Summary
- Adapts the moved `codex-cloud-config` crate from the legacy cloud
requirements endpoint to the new config bundle endpoint.
- Switches runtime consumers from `CloudRequirementsLoader` to
`CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered
config and requirements.
- Removes the legacy cloud requirements domain loader path.
## Details
This intentionally keeps `codex-cloud-config` monolithic for review
lineage: the previous PR establishes the crate move, and this PR shows
the behavior change against that moved implementation. A follow-up PR
splits the module back into focused files.
The new bundle path preserves the important cloud requirements loader
semantics where intended: account-scoped signed cache, 30 minute TTL, 5
minute refresh cadence, retry/backoff, auth recovery, and fail-closed
startup loading. The cached payload changes from a single requirements
TOML string to the backend-delivered bundle, and validation rejects
malformed config or requirements fragments before cache write/use.
## Summary
- carry `workspace_kind` from Responses API client metadata into the
turn resolved analytics fact
- serialize the optional value on `codex_turn_event`
- cover both the turn metadata source and turn event serialization
The `workspace_kind` tells us whether a thread had a project attached vs
projectless. this is an indicator for who is adopting Codex for
knowledge work outside of coding
## Testing
- `env UV_CACHE_DIR=/private/tmp/uv-cache
/private/tmp/cargo-tools/bin/just fmt`
- `env PATH=/private/tmp/cargo-tools/bin:$PATH
CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
/private/tmp/cargo-tools/bin/just test -p codex-analytics`
- `env PATH=/private/tmp/cargo-tools/bin:$PATH
CARGO_HOME=/private/tmp/cargo-home UV_CACHE_DIR=/private/tmp/uv-cache
/private/tmp/cargo-tools/bin/just test -p codex-core turn_metadata`
Paired with openai/openai#970661, which keeps forwarding the same
metadata key through Responses API headers.
## Why
Fixes#24944.
On Windows, app-server resume could reject an active running thread when
the requested session path used normal `C:\...` form and the
already-running path used verbatim `\\?\C:\...` form. The paths point at
the same JSONL file, but the resume stale-path guard compared raw
`PathBuf`s, so desktop resume and heartbeat flows could fail with a
mismatched-path error.
## What Changed
- Compare requested and active rollout paths with
`path_utils::paths_match_after_normalization`.
- Extend the existing running-thread mismatched-path test with a
Windows-only same-file resume case before the stale-path rejection.
## Verification
- `just test -p codex-app-server
thread_resume_rejects_mismatched_path_for_running_thread_id`
## Stack
1. #25850 - Key request-permission grants by environment: stores and
applies sticky permission grants per environment id.
2. #25858 - Add `environmentId` to `request_permissions`: lets the model
target a selected environment and resolves relative permission paths
against it.
3. This PR (#25862) - Propagate permission approval environment id:
carries the selected environment id through approval events, app-server
requests, TUI prompts, and delegate forwarding.
4. #25867 - Add remote request permissions integration coverage:
verifies the selected remote environment across request, approval, grant
reuse, and exec.
This PR is stacked on #25858, and #25867 is stacked on this PR.
## Why
PR2 lets the model bind a `request_permissions` call to a selected
environment, but the approval event and client-facing request still
needed to carry that binding. For CCA, the user-facing prompt and
delegated approval path should know which environment the grant applies
to instead of relying on cwd alone.
## What Changed
- Added optional `environmentId` to `RequestPermissionsEvent`.
- Emit the selected environment id from core permission approval events.
- Preserve the environment id through delegate forwarding, including
cwd-based delegated requests.
- Added `environmentId` to app-server permission approval params,
generated schema/TypeScript artifacts, and README examples.
- Preserve and display the environment id in TUI permission approval
prompts.
- Updated focused core, app-server protocol, and TUI conversion
coverage.
## Testing
Not run locally per instruction. Performed read-only `git diff --check`.
## Why
Standalone image-generation extensions emitted turn items through the
low-level event path, bypassing host-owned finalization such as image
persistence and contributor processing. At the same time, the
generated-image save-path hint must remain visible to the model through
the extension tool's `FunctionCallOutput`, rather than the legacy
built-in developer-message path.
## What changed
- Extended `ExtensionTurnItem` to support image-generation items while
keeping the extension-facing emitter API limited to `emit_started` and
`emit_completed`.
- Routed extension completion through core `finalize_turn_item`, so
standalone image-generation items receive host-owned processing and
persisted `saved_path` values before publication.
- Kept legacy built-in image generation on its existing
developer-message hint path, while standalone image generation returns
its deterministic saved-path hint in `FunctionCallOutput`.
- Shared the image artifact path and output-hint formatting used by core
and the image-generation extension.
- Passed thread identity through extension tool calls so standalone
image generation can construct the same intended artifact path as core.
- Added an app-server integration test covering real standalone image
generation, saved artifact publication, model-visible output hint
wiring, and absence of the legacy developer-message hint.
## Validation
- `just fmt`
- `just test -p codex-image-generation-extension`
- `just test -p codex-web-search-extension`
- `just test -p codex-goal-extension`
- `just test -p codex-memories-extension`
- Targeted `codex-core` tests for image save history, extension
completion finalization, and contributor execution
- `just test -p codex-app-server
standalone_image_generation_returns_saved_path_hint_to_model`
- `just fix -p codex-core`
- `just fix -p codex-image-generation-extension`
- `just bazel-lock-update`
- `just bazel-lock-check`
Stack split from #25708. Original PR intentionally left open. This
second PR persists multi-agent runtime metadata through thread creation,
rollout recording, and thread storage.
Stack split from #25708. Original PR intentionally left open. This first
PR adds the multi-agent runtime metadata types and catalog plumbing used
by the rest of the stack.
## Summary
- cache the global remote plugin catalog when remote plugin listing runs
and warm it during startup
- use the cached remote catalog in plugin install recommendations with
canonical `plugin@openai-curated-remote` ids
- reuse the session `PluginsManager` for plugin recommendations so
remote cache state is visible on the recommend path
- skip core installed-state verification for remote plugin install
suggestions while leaving local plugin and connector verification
unchanged
## Testing
- `just fmt`
- `git diff --check`
- `cargo test -p codex-core
list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins`
- `cargo test -p codex-core
remote_plugin_install_suggestions_skip_core_installed_verification`
- `cargo test -p codex-app-server
plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`
Earlier focused checks during the same branch: codex-tools TUI filter
test, request_plugin_install tests, and codex-app-server build.
## Summary
Enterprise users can have an effective monthly credit limit, but Codex
`/status` currently drops that metadata from the account-usage response.
This change adds the optional `spend_control.individual_limit`
projection to the existing rate-limit snapshot flow. The backend client
reads the monthly limit, app-server exposes it as `individualLimit`, and
the TUI renders a `Monthly credit limit` row through the existing
progress-bar renderer.
When the backend does not return an effective monthly limit, existing
rate-limit behavior is unchanged.
## Existing backend state
The account-usage backend already returns the effective monthly limit
and current usage together:
```json
{
"spend_control": {
"reached": false,
"individual_limit": {
"limit": "25000",
"used": "8000",
"remaining": "17000",
"used_percent": 32,
"remaining_percent": 68,
"reset_after_seconds": 86400,
"reset_at": 1778137680
}
}
}
```
Before this change, Codex projected rolling `primary` and `secondary`
windows plus `credits`. It ignored `spend_control.individual_limit`, so
app-server clients and `/status` could not render the monthly cap.
The updated flow is:
```text
account usage backend
-> backend-client reads spend_control.individual_limit
-> existing rate-limit snapshot carries optional individual_limit
-> app-server exposes optional individualLimit
-> TUI renders Monthly credit limit
```
## App-server contract
`account/rateLimits/read` and sparse `account/rateLimits/updated`
notifications now include an additive nullable
`rateLimits.individualLimit` field:
```json
{
"individualLimit": {
"limit": "25000",
"used": "8000",
"remainingPercent": 68,
"resetsAt": 1778137680
}
}
```
In an `account/rateLimits/read` response, `null` means no monthly limit
is available. `account/rateLimits/updated` remains a sparse rolling
notification: clients merge available values into their most recent
`account/rateLimits/read` snapshot or refetch. Nullable account metadata
in a rolling notification does not clear a previously observed value.
## Design decisions
- Extend the existing rate-limit snapshot instead of introducing a
separate request or wire-level update protocol.
- Keep the Codex projection narrow: `/status` needs the effective limit,
current usage, remaining percentage, and reset timestamp.
- Render the monthly row through the existing progress-bar renderer,
with one optional detail line for `8,000 of 25,000 credits used`.
- Keep the backend response optional so existing accounts and older
usage states preserve their current behavior.
- Preserve cached monthly metadata when sparse rolling notifications
omit it. Live account-usage reads remain authoritative and can clear a
removed limit.
## Visual evidence
```text
Monthly credit limit: [██████████████░░░░░░] 68% left (resets 07:08 on 7 May)
8,000 of 25,000 credits used
```
Snapshot:
`codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_enterprise_monthly_credit_limit.snap`
## Testing
Tests: generated app-server schema verification, protocol tests,
backend-client tests, app-server integration coverage, TUI snapshot
coverage, formatting, and workspace lint cleanup.
## Why
Remote control enrollment authorizes a desktop server, but app-server v2
did not expose the follow-up pairing operation needed to mint a
short-lived controller pairing artifact from that enrolled server.
Clients need a narrow RPC that starts pairing without exposing the
backend `serverId` or conflating pairing with websocket connection
state.
Issue: N/A; internal remote-control pairing API change.
## What Changed
Added experimental app-server v2 `remoteControl/pairing/start` with
`manualCode` input and `pairingCode`, nullable `manualPairingCode`,
`environmentId`, and Unix-seconds `expiresAt` output. The method
serializes under its own `global("remote-control-pairing")` scope and is
documented in `app-server/README.md`.
Extended the remote-control transport with private `/server/pair`
request/response types and normalized `pair_url` handling. Pairing uses
the current enrolled server bearer, refreshes that bearer when needed,
keeps backend `server_id` private, validates returned `server_id` and
`environment_id` against the current enrollment, and preserves backend
status/header/body context for failures and malformed responses.
Wired the request through `RemoteControlRequestProcessor` and
`MessageProcessor`, mapping unavailable/disabled pairing to
`invalid_request` and backend failures to internal errors.
## Verification
- `just test -p codex-app-server-transport`
- `just test -p codex-app-server
remote_control_pairing_start_returns_pairing_artifacts`
## Summary
- Moves the existing `codex-cloud-requirements` crate to
`codex-cloud-config`.
- Updates workspace dependencies and imports to the new crate name.
- Intentionally keeps runtime behavior unchanged: this still fetches the
legacy cloud requirements endpoint.
## Details
This PR exists to make the lineage obvious before the bundle migration.
GitHub should show the old `codex-rs/cloud-requirements/src/lib.rs`
implementation as moved to `codex-rs/cloud-config/src/lib.rs`, rather
than as unrelated new code.
The follow-up PR adapts this moved crate to the new config bundle API
and switches runtime consumers over.
## Summary
Remove the dead experimental `persistExtendedHistory` app-server flag
and collapse rollout persistence to the single policy app-server already
used.
## What Changed
- Removed `persistExtendedHistory` from v2 thread start/resume/fork
params and deleted its deprecation notice path.
- Removed the persistence-mode enums and plumbing through core, rollout,
and thread-store.
- Made rollout filtering mode-free, keeping the existing limited
persisted-history behavior.
## Test Plan
- `just write-app-server-schema`
- `cargo nextest run --no-fail-fast -p codex-app-server-protocol
schema_fixtures`
- `cargo nextest run --no-fail-fast -p codex-app-server
thread_shell_command_history_responses_exclude_persisted_command_executions`
- `cargo nextest run --no-fail-fast -p codex-rollout -p
codex-thread-store`
- final `rg` for removed flag/type names
## Why
Fixesopenai/codex#20944.
Desktop side chats are intentionally ephemeral and pathless. They can
still accept live turns while loaded, but after a reload there is no
persisted rollout to resume. In the reported failure mode, Desktop could
send `$CODEX_HOME` as the resume/fork path for one of these pathless
side chats.
`thread/resume` and `thread/fork` prefer an explicit `path` over
`threadId`, and rollout path lookup only checked that a candidate
existed. That let `$CODEX_HOME` pass as a rollout path, so the later
rollout reader tried to open a directory and surfaced the low-level `Is
a directory` error.
## What Changed
- Reject explicit rollout paths that resolve to a directory or other
non-file before attempting to read rollout history.
- Make `codex_rollout::existing_rollout_path` return only plain or
compressed rollout candidates that are actual files.
- Add an app-server regression test that creates an ephemeral fork, runs
a turn while the side thread is loaded, simulates reload, then verifies
both `thread/resume` and `thread/fork` reject `$CODEX_HOME` with `path
is a directory` instead of the OS-level directory-read error.
- Rebase over the `TestAppServer` rename and update the remaining stale
test harness call sites to use `TestAppServer` with `app_server` local
variables.
Relevant code:
- `thread-store/src/local/read_thread.rs` validates explicit rollout
paths before rollout reading:
https://github.com/openai/codex/blob/25b47c8f425d351aaba4baa955a8092064a1707b/codex-rs/thread-store/src/local/read_thread.rs#L146-L165
- `rollout/src/compression.rs` now requires file metadata for plain and
compressed rollout candidates:
https://github.com/openai/codex/blob/25b47c8f425d351aaba4baa955a8092064a1707b/codex-rs/rollout/src/compression.rs#L940-L950
- The repro test covers the pathless ephemeral side-chat reload case:
https://github.com/openai/codex/blob/25b47c8f425d351aaba4baa955a8092064a1707b/codex-rs/app-server/tests/suite/v2/thread_fork.rs#L774-L886
## Verification
- `just test -p codex-app-server
pathless_ephemeral_thread_rejects_codex_home_path_after_reload`
## Why
#25701 renamed the app-server test harness to `TestAppServer`, but it
raced with #25681, which added a new `plugin_list` test call site still
using the old `McpProcess` name. Once both changes met on `main`,
app-server test builds failed before running the suite because
`McpProcess` no longer exists in that scope.
This PR fixes that CI break by updating the remaining stale call site to
the renamed helper.
## What Changed
- Replaced the `McpProcess::new(...)` use in
`codex-rs/app-server/tests/suite/v2/plugin_list.rs` with
`TestAppServer::new(...)`.
- Renamed the local variable from `mcp` to `app_server` at the same call
site to match the helper rename.
Relevant code:
https://github.com/openai/codex/blob/aadd9c999b4e0789f7afb2b9b8cc43000bb47e86/codex-rs/app-server/tests/suite/v2/plugin_list.rs#L234-L246
## Verification
Not run locally; this is a compile fix for the app-server test harness
rename.
This PR brought to you via VS Code rather than Codex...
- opened `codex-rs/app-server/tests/common/mcp_process.rs`
- put the cursor on `McpServer`
- hit `F2` and renamed the symbol to `TestAppServer`
- went to the file tree
- hit enter and renamed `mcp_process.rs` to `test_app_server.rs`
- ran **Save All Files** from the Command Palette
- ran `just fmt`
The End
(Admittedly, most of the local variables for `TestAppServer` are still
named `mcp`, though.)
## Why
Guardian auto-review normally uses the provider-preferred review model
when one is available. Some parent models need model-catalog metadata to
select a different review model while keeping older `/models` payloads
compatible when that metadata is absent.
## What changed
- Added optional `ModelInfo::auto_review_model_override` metadata to the
public model payload as a review-model slug.
- Updated Guardian review model selection to prefer the catalog override
when present, while preserving the existing provider preferred-model
path and parent-model fallback when it is omitted.
- Added focused Guardian coverage for override and no-override model
selection.
- Added an `auto_review` core integration suite test that loads override
metadata from a remote model catalog path and asserts the strict
auto-review `/responses` request uses the catalog-selected review model.
- Updated existing `ModelInfo` fixtures and local catalog constructors
for the new optional field.
## Validation
- `cargo test -p codex-protocol
model_info_defaults_availability_nux_to_none_when_omitted`
- `cargo test -p codex-core guardian_review_uses_`
- `cargo test -p codex-core
remote_model_override_uses_catalog_model_for_strict_auto_review --test
all`
- `just fix -p codex-protocol`
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`
## Why
This PR
https://github.com/openai/codex/pull/24161#discussion_r3325692763
revealed a subagent data modeling issue, where we overloaded
`forked_from_id` to also mean `parent_thread_id`. That's incorrect since
guardian and review subagents can be a subagent and NOT fork the main
thread's history.
The solution here is to explicitly store a new `parent_thread_id` on
`SessionMeta`, alongside `forked_from_id` which already exists. While
we're at it, also expose it in the app-server protocol on the `Thread`
object.
A thread->subagent relationship and a fork of thread history are
orthogonal concepts.
## What Changed
- Added top-level `parent_thread_id` persistence on `SessionMeta` and
runtime/session plumbing through `SessionConfiguredEvent`,
`CodexSpawnArgs`, `SessionConfiguration`, `ThreadConfigSnapshot`,
`TurnContext`, and `ModelClient`.
- Made turn metadata, request headers, analytics, and subagent-start
events read the separate runtime/top-level parent field instead of
deriving general parent lineage from `SessionSource` or
`forked_from_thread_id`.
- Passed parent lineage separately at delegated subagent, review,
guardian, agent-job, and multi-agent spawn construction sites;
copied-history fork lineage remains derived only from `InitialHistory`.
- Persisted and exposed parent lineage through rollout/thread-store
projections and app-server v2 `Thread.parentThreadId`.
- Updated app-server README text and regenerated app-server schema
fixtures for the additive `parentThreadId` response field.
## Summary
PR 3 of 5 in the cloud-managed config client stack.
Adds enterprise-managed cloud config as a first-class config layer
source. The layer metadata is preserved through config loading,
diagnostics, debug output, hook attribution, and app-server protocol
surfaces.
## Details
- Enterprise-managed config becomes a normal config layer source with
backend-supplied `id` and display `name` attached for provenance.
- These layers are designed to behave like non-file managed config: they
can surface syntax/type diagnostics by layer name even though there is
no physical config file.
- Relative path settings are resolved from a stored config base so
cloud-delivered config remains consistent with existing MDM-delivered
config semantics.
- Hook attribution distinguishes config-delivered hooks from
requirements-delivered hooks via `HookSource::CloudManagedConfig`.
- This remains pull-based and snapshot-oriented; the PR adds layer
identity/diagnostics, not dynamic reload behavior.
## Validation
Validated through the targeted stack checks after rebasing onto current
`main`:
- Rust crate tests for
config/hooks/cloud-config/backend-client/app-server-protocol
- Filtered `codex-core` and `codex-app-server` `cloud_config_bundle`
tests
- Python generated-file contract test
- `cargo shear --deny-warnings`
- Targeted `argument-comment-lint` for config/hooks
## Summary
- Use normal directory loading for plugin install app metadata so
install avoids forced directory refresh while still loading metadata on
cold cache.
- Continue force-refreshing codex_apps tools for auth state.
- Add regression coverage that pre-warms the directory cache and asserts
install returns cached app metadata without extra directory requests.
## Validation
- just fmt
- git diff --check
- just test -p codex-app-server plugin_install_returns_apps_needing_auth
plugin_install_filters_disallowed_apps_needing_auth (blocked locally:
cargo-nextest is not installed)
## Problem
Saved threads can already be archived through app-server RPCs, but the
command line did not expose direct archive or unarchive commands.
## Solution
Add `codex archive <thread>` and `codex unarchive <thread>`, resolving
UUIDs or exact thread names before calling the existing `thread/archive`
and `thread/unarchive` RPCs. The commands support scoped remote flags so
callers can target remote app-server endpoints when archiving or
unarchiving threads.
This also fixes a long-standing bug in `codex resume <thread id>` and
`codex fork <thread id>` that I found when testing the new commands.
These operations shouldn't be allowed on archived sessions. They now
fail with an error that tells the user to run `codex unarchive <thread
id>` first.
## Verification
Added app-server coverage for rejecting archived thread resume by id and
checking that the error includes the matching `codex unarchive <thread
id>` command.
# Why
Managed requirements can already constrain sandbox policy choices, but
Windows sandbox implementation selection was still resolved
independently from those requirements. That left the TUI able to
continue through the unelevated fallback even when an organization wants
to require the elevated Windows sandbox implementation.
# What
- Add `[windows].allowed_sandbox_implementations` requirements support
for the Windows `elevated` and `unelevated` implementations.
- Apply that allowlist during core config resolution so disallowed
configured or feature-selected Windows sandbox implementations fall back
to an allowed implementation with the existing requirements warning
path.
- Reuse the existing TUI Windows setup prompts to block disallowed
unelevated continuation, keep required elevated setup in front of the
user, and refuse to persist a TUI-selected Windows sandbox mode that
requirements disallow.
# Semantics
| Allowed | Selected | Effective |
| --- | --- | --- |
| `["elevated"]` | `unelevated` / unset | `elevated` |
| `["unelevated"]` | `elevated` / unset | `unelevated` |
| `["elevated", "unelevated"]` | `elevated` | `elevated` |
| `["elevated", "unelevated"]` | `unelevated` | `unelevated` |
| `["elevated", "unelevated"]` | unset | `elevated` |
Availability is handled by interactive setup surfaces after allowlist
resolution. If the effective elevated implementation is not ready,
elevated-only requirements block on setup. When unelevated is also
allowed, the UI may offer the existing unelevated fallback.
## TUI Screens
If elevated setup is not already complete:
```
Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control
network access.
Learn more <https://developers.openai.com/codex/windows>
› 1. Set up default sandbox (requires Administrator permissions)
2. Quit
```
If admin setup fails under `["elevated"]`:
```
Couldn't set up your sandbox with Administrator permissions
Your organization requires the default sandbox before Codex can continue.
Learn more <https://developers.openai.com/codex/windows>
› 1. Try setting up admin sandbox again
2. Quit
```
# Next Steps
- extend the requirements/readout surface, such as
`configRequirements/read`, so clients can inspect the loaded
`[windows].allowed_sandbox_implementations` requirement instead of
inferring it from Windows setup state
- consider extending `windowsSandbox/readiness` as well
- update the App startup guide, setup flow, and banner surfaces so an
elevated-only requirement omits any continue-unelevated escape hatch and
blocks startup until a permitted implementation is ready;
- preserve the existing unelevated fallback path when requirements allow
it, including the `["unelevated"]` case where elevated is disallowed
## Why
The standalone `/v1/alpha/search` request now requires a `model`, but
the `web.run` extension currently omits it.
Adds `model` to extension `ToolCall` invocation.
Follow-up to #23823.
## What changed
- Make `SearchRequest.model` required.
- Expose the effective per-turn model on extension tool calls and pass
it in standalone web-search requests.
- Assert the model is forwarded in the app-server round-trip test.
## Testing
- `just test -p codex-api -p codex-tools -p codex-web-search-extension
-p codex-memories-extension -p codex-goal-extension`
- `just test -p codex-core -E
'test(passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call)'`
- `just test -p codex-app-server -E
'test(standalone_web_search_round_trips_encrypted_output)'`
## Why
`SandboxPolicy` is the legacy compatibility shape, but
`codex-thread-store` still exposed it through `StoredThread`,
`ThreadMetadataPatch`, and live metadata sync. That kept thread-store
consumers tied to the legacy representation and meant richer permission
profile data could not round-trip through thread metadata or cold
rollout reconciliation.
## What Changed
- Replaced thread-store `sandbox_policy` API fields with canonical
`PermissionProfile` fields.
- Persist new permission-profile metadata as canonical JSON in the
existing SQLite metadata slot while continuing to read older legacy
sandbox policy values.
- Updated local, in-memory, live metadata sync, and rollout extraction
paths to propagate `TurnContextItem::permission_profile()`.
- Re-materialize legacy permission metadata against the final rollout
cwd when rollout-derived metadata replaces stale SQLite summaries.
- Updated affected app-server and core test constructors to build
`PermissionProfile` values directly.
## Test Plan
- `cargo test -p codex-state`
- `cargo test -p codex-thread-store`
- `cargo test -p codex-app-server
summary_from_stored_thread_preserves_millisecond_precision --lib`
- `cargo test -p codex-core realtime_context --lib`
## Why
We recently added `forked_from_thread_id` which lets us trace where a
thread's _context_ comes from, but we also want to understand subagent
lineage (e.g. which parent thread spawned this subagent? what kind of
subagent is it?) which is orthogonal.
This PR adds `parent_thread_id` and `subagent_kind` to the
`x-codex-turn-metadata` header sent to ResponsesAPI.
## What changed
- Adds `parent_thread_id` and `subagent_kind` to core-owned
`x-codex-turn-metadata`.
- Restores persisted `SessionSource` and `ThreadSource` from resumed
session metadata so cold-resumed subagent threads keep their lineage on
later Responses API requests.
- Centralizes parent-thread extraction on `SessionSource` /
`SubAgentSource` and reuses it in the Responses client, analytics, agent
control, and state parsing paths.
- Extends reserved-key, git-enrichment, thread-spawn, and app-server v2
metadata coverage for the new lineage fields.
## Verification
- Not run locally per request.
- Added focused coverage in `core/src/turn_metadata_tests.rs` and
`app-server/tests/suite/v2/client_metadata.rs`.
## Why
Standalone `web.run` calls run in the extension, so they need normal
web-search progress activity while a request is in flight and durable
completed activity after a thread is reloaded.
Follow-up to #23823; uses the extension turn-item emission path added in
#24813.
## What changed
- Emit standalone `web.run` start/completion items through the host
turn-item emitter, preserving standard client delivery and rollout
persistence.
- Include useful completion detail for queries, image queries, and
literal-URL `open`/`find` commands.
- Render completed searches as `Searched the web` or `Searched the web
for <detail>`, with snapshot coverage for the detail-free case.
- Extend the app-server round-trip test to verify completed search
activity is reconstructed by `thread/read` after a fresh-process reload.
## Testing
- `just test -p codex-web-search-extension`
- `just test -p codex-app-server -E
"test(standalone_web_search_round_trips_encrypted_output)"`
## Why
Some models need to select their code-execution behavior through model
catalog metadata. Models without that metadata must continue to follow
the existing `CodeMode` and `CodeModeOnly` feature flags, including when
a newer server sends an enum value this client does not recognize.
## What changed
- add optional `ModelInfo.tool_mode` metadata with `direct`,
`code_mode`, and `code_mode_only`
- treat omitted and unknown wire values as `None`
- resolve `None` from the existing feature flags
- carry the resolved `ToolMode` directly on `TurnContext`, outside
`Config`
- use the resolved value for turn creation, model switches, review
turns, tool planning, and code execution
## Coverage
- add protocol coverage for omitted, known, and unknown enum values
- add focused coverage for flag fallback and explicit metadata
overriding feature flags
- add core integration coverage that fetches remote model metadata
through `/v1/models` and verifies the outbound `/responses` tools for
explicit `direct` and `code_mode_only` selectors
## Stack
- followed by #25032
## Summary
`fs/watch` was using a local debounce wrapper whose deadline was
initialized once and then reused after the first batch. Once that stale
deadline was in the past, later file changes could bypass the intended
200ms debounce and send noisier `fs/changed` notifications.
This moves the debounce wrapper into `codex-file-watcher` as
`DebouncedWatchReceiver`, resets the debounce deadline for each event
batch, preserves pending paths across cancelled receives, and updates
app-server `fs/watch` to use the shared wrapper.
Fixes#24692.
## Why
#23813 switches the Windows sandbox runner path to `PermissionProfile`,
but it still left one runtime anchor for resolving symbolic
`:workspace_roots` entries. That is not enough once a turn has multiple
effective workspace roots: exact entries and deny globs under
`:workspace_roots` need to be materialized for every runtime root before
the command runner chooses token mode or builds ACL plans.
## What Changed
- Replaces the Windows runner/setup `permission_profile_cwd` plumbing
with `workspace_roots: Vec<AbsolutePathBuf>`.
- Resolves Windows-local `PermissionProfile` data with
`materialize_project_roots_with_workspace_roots(...)` instead of the
single-cwd helper.
- Threads `Config::effective_workspace_roots()` through core execution,
unified exec, TUI setup/read-grant flows, app-server setup, app-server
`command/exec`, and `debug sandbox` on Windows.
- Preserves those workspace roots through the zsh-fork escalation
executor instead of rebuilding them from `sandbox_policy_cwd`.
- Makes `ExecRequest::new(...)` and the remaining
`build_exec_request(...)` helper path take
`windows_sandbox_workspace_roots` explicitly so new call sites cannot
silently fall back to `vec![cwd]`.
- Clarifies the `debug sandbox` non-Windows comment: remaining
cwd-dependent resolution still uses `sandbox_policy_cwd`, while
`:workspace_roots` entries are already materialized from config roots.
- Updates elevated runner IPC `SpawnRequest` to send `workspace_roots`
and bumps the framed IPC protocol version to `3` for the payload shape
change.
- Adds Windows-local resolver coverage for expanding exact and glob
`:workspace_roots` entries across multiple roots, plus core helper
coverage proving explicit roots are preserved.
## Verification
- `cargo check -p codex-windows-sandbox -p codex-core -p codex-tui -p
codex-cli -p codex-app-server`
- `cargo test -p codex-windows-sandbox`
- `cargo test -p codex-core windows_sandbox`
- `cargo test -p codex-core unix_escalation`
- `cargo test -p codex-app-server windows_sandbox`
- `cargo test -p codex-tui windows_sandbox`
- `cargo test -p codex-cli debug_sandbox`
- `just test -p codex-core unified_exec`
- `just test -p codex-core
build_exec_request_preserves_windows_workspace_roots`
- `env -u CODEX_NETWORK_PROXY_ACTIVE -u
CODEX_NETWORK_ALLOW_LOCAL_BINDING just test -p codex-app-server --lib
command_exec`
- `just test -p codex-windows-sandbox`
- `just test -p codex-exec sandbox`
- `just fix -p codex-core -p codex-app-server -p codex-windows-sandbox`
A local macOS cross-check with `cargo check --target
x86_64-pc-windows-msvc ...` did not reach crate Rust code because native
dependencies require Windows SDK headers (`windows.h` / `assert.h`) in
this environment; Windows CI remains the real target validation.
Two local targeted filters compile but do not run assertions on macOS:
`env -u CODEX_NETWORK_PROXY_ACTIVE -u CODEX_NETWORK_ALLOW_LOCAL_BINDING
just test -p codex-app-server --lib command_exec_processor` matched zero
tests, and `just test -p codex-linux-sandbox landlock` matched zero
tests because the landlock suite is Linux-only.
## Summary
Adds an optional `clientId` field to app-server v2 `UserInput` and
carries it through the core `UserInput` model so clients can correlate
echoed user input items without relying on payload equality.
## Details
- Adds `client_id: Option<String>` to core `UserInput` variants.
- Exposes the v2 app-server field as `clientId` on the wire and in
generated TypeScript.
- Preserves the id when converting between app-server v2 and core
protocol types.
- Regenerates app-server schema fixtures.
## Validation
- `just fmt`
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-protocol`
- `just fix -p codex-app-server-protocol`
- `just fix -p codex-protocol`
- `git diff --check`
## Summary
- Add `--stdio` as a direct alias for `codex app-server --listen
stdio://`.
- Keep `--stdio` and `--listen` mutually exclusive.
- Update the app-server README to document both forms.
## Why
Add a standalone image generation path that can be exercised
independently of hosted Responses image generation, while retaining the
hosted tool as fallback unless the extension is actually available to
the model.
## What changed
- Added the `codex-image-generation-extension` crate with standalone
generate/edit execution, prior-image selection for edits, model-visible
image output, and local generated-image persistence.
- Installed the extension in app-server behind the disabled-by-default
`imagegenext` feature and backend eligibility checks.
- Updated core tool planning so eligible `image_gen.imagegen` exposure
replaces hosted `image_generation`, while unavailable configurations
retain hosted fallback.
- Added coverage for extension behavior, edit history reuse, feature
gating, auth eligibility, and hosted-tool replacement.
- The extension is installed through app-server only in this PR; other
execution paths retain hosted image generation because hosted
replacement occurs only when the standalone executor is actually
registered and model-visible.
- The initial extension contract intentionally fixes the image model to
`gpt-image-2` and uses automatic image parameters.
- Native generated-image history/card parity and rollout persistence
cleanup are intentionally deferred follow-up work.
## Validation
- `just test -p codex-image-generation-extension`
- `just test -p codex-features`
- `just test -p codex-core
hosted_tools_follow_provider_auth_model_and_config_gates`
- `just test -p codex-app-server`
- `just fix -p codex-image-generation-extension -p codex-features -p
codex-core -p codex-app-server`
- `just fmt`
- `just bazel-lock-update`
- `just bazel-lock-check`
---------
Co-authored-by: jif-oai <jif@openai.com>
## Summary
The client currently calls `thread/resume` to establish live updates and
immediately follows it with `thread/turns/list` to hydrate recent turns.
This lets `thread/resume` return that page directly, eliminating a round
trip and the ordering/deduplication gap between the two calls.
Experimental clients opt in with `initialTurnsPage: { limit,
sortDirection, itemsView }`. The response returns `initialTurnsPage` as
a `TurnsPage`, including cursors for paging further back in history.
Keeping the controls in a nested opt-in object provides the useful
`thread/turns/list` knobs without spreading page-specific parameters
across `thread/resume`.
## Verification
- `just fmt`
- `just write-app-server-schema --experimental`
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server
thread_resume_initial_turns_page_matches_requested_turns_list_page
--tests`
- `cargo test -p codex-app-server
thread_resume_rejoins_running_thread_even_with_override_mismatch
--tests`
- `just fix -p codex-app-server-protocol -p codex-app-server`