## Description
This PR adds an optional `metadata` field to `ResponseItem` for
Responses API calls. Only mechanical plumbing, no actual values
populated and sent yet. Turns out just adding a new field to
`ResponseItem` has quite a large blast radius already.
This change is backwards compatible because `metadata` is optional and
omitted when absent, so existing response items and rollout history
without it still deserialize and requests that do not set it keep the
same wire shape. For provider compatibility, we strip out `metadata`
before non-OpenAI Responses requests so Azure and AWS Bedrock never see
this field.
My followup PR here will actually make use of it to start storing and
passing along `turn_id`: https://github.com/openai/codex/pull/28360
## What changed
- Added `ResponseItemMetadata` with optional `turn_id`, plus optional
`metadata` on Responses API item variants and inter-agent communication.
- Preserved item metadata through response-item rewrites such as
truncation, missing tool-output synthesis, compaction history
rebuilding, visible-history conversion, rollout/resume, and generated
app-server schemas/types.
- Strip item metadata from non-OpenAI Responses requests while
preserving it for OpenAI-shaped requests.
- Updated the mechanical fixture/test construction churn required by the
new optional field.
## Why
Codex users can earn personal rate-limit reset credits, but app-server
clients do not currently have an API for reading or redeeming them. This
adds the backend and protocol foundation used by the `/usage` TUI flow
in #28154.
## What changed
- Extend `account/rateLimits/read` with a nullable
`rateLimitResetCredits` summary sourced from the existing usage
response.
- Add backend-client and app-server support for consuming a reset with a
caller-generated idempotency key. A UUID is recommended, and clients
reuse the same key when retrying the same logical reset.
- Return only the consume `outcome`; clients refetch
`account/rateLimits/read` for updated window state.
- Document the response field and each consume outcome, and regenerate
the JSON and TypeScript schema fixtures.
- Clarify in `AGENTS.md` that new app-server string enum values use
camelCase on the wire.
- Update the existing TUI response fixture for the expanded protocol
shape.
- Add coverage for authentication, response mapping, backend failures,
consume outcomes, and request timeout behavior.
## Validation
- `just test -p codex-app-server-protocol` — 231 passed.
- `just test -p codex-backend-client` — 14 passed.
- Focused `codex-app-server` reset-credit tests — 5 passed.
- Focused `codex-tui` protocol response fixture test — passed.
- `just fix -p codex-backend-client -p codex-app-server-protocol -p
codex-app-server` — passed.
- `just fmt` — passed.
## Summary
- Add TUI auto-resolution handling for `request_user_input` prompts when
`autoResolutionMs` is present.
- Use a 60s hidden grace period followed by a 60s visible countdown,
then submit an empty answer response if the user does not interact.
- Snooze auto-resolution on key or paste interaction and add
snapshot/test coverage for the countdown UI.
## Notes
- The TUI currently treats `autoResolutionMs` as an enable signal and
intentionally does not use the provided duration value for the countdown
policy.
### Auto resolution
https://github.com/user-attachments/assets/5323152f-2ece-4aba-b75d-c32aa776f544
### Snooze after interaction
https://github.com/user-attachments/assets/100d54c4-3a41-4c6c-9c07-cd28075a0d62
This adds the background plumbing for remote-backed plugin catalog
sections while leaving the fuller directory presentation to the next PR.
The TUI can fetch section-specific remote marketplace results, keep
local plugin data available, and carry section errors forward for later
rendering.
- Fetches explicit remote marketplace kinds for curated, workspace, and
shared-with-me sections.
- Gates shared-with-me loading on the plugin sharing feature flag.
- Adds section-level error state and user-actionable error copy.
- Merges remote marketplace results into the cached plugin list without
discarding local results.
## Why
`terminal_resize_reflow` is now stable and should behave as always on.
Keeping the disabled runtime paths around made the feature look
configurable even though the rollout is complete, and old config could
still suggest there was a supported off mode.
## What Changed
- Marked `terminal_resize_reflow` as `Stage::Removed` while keeping it
default-enabled for compatibility.
- Ignored `[features].terminal_resize_reflow` config entries so stale
`false` settings no longer affect the effective feature set.
- Removed TUI branches that depended on the flag being disabled, so
draw, replay buffering, stream finalization, and resize scheduling all
assume resize reflow is active.
- Simplified resize smoke coverage to exercise the always-on behavior
only.
## Verification
- `just test -p codex-features`
- `just test -p codex-tui resize_reflow`
- `just test -p codex-tui initial_replay_buffer
thread_switch_replay_buffer`
## Why
Clients that display or coordinate spawned subagents need an
authoritative snapshot of a thread's immediate spawned children when
they connect to app-server or recover after missing live events.
`thread/list` cannot query by parent, so clients must otherwise scan
unrelated threads or reconstruct relationships from rollout history and
transient events.
The direct spawn relationship already exists in persisted
`thread_spawn_edges` state. Review and Guardian threads do not
participate in that lifecycle and are intentionally outside this
filter's scope.
## What changed
This adds an experimental `parentThreadId` filter to `thread/list`.
Parent-filtered requests return direct spawned children from persisted
state while preserving the existing response shape, explicit filters,
sorting, and timestamp-only cursor behavior. The lookup does not read
rollout transcripts or recursively return descendants.
Supersedes #25112 with the narrower `thread/list` filter approach.
## How it works
1. An experimental client passes a valid thread ID as `parentThreadId`.
2. App-server routes the list through the existing thread-store and
state-database boundaries.
3. SQLite selects threads whose IDs have a direct persisted spawn edge
from that parent.
4. Omitted provider and source filters include all values; explicit
filters keep ordinary `thread/list` semantics.
5. Grandchildren, Review threads, and Guardian threads are excluded.
## Verification
State (144 tests), rollout (69 tests), and focused app-server
thread-list (31 tests) suites passed. Scoped Clippy checks and
repository formatting also passed. Coverage includes direct spawned
children, omitted grandchildren, pagination, malformed IDs, mixed source
kinds, explicit filters, and operation without rollout files.
## Intent
Keep Bazel and Starlark files consistently formatted without requiring
contributors to install or version buildifier themselves.
## Implementation
- Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
v8.5.1.
- Run buildifier from the shared `just fmt` and `just fmt-check` driver,
with Windows-safe explicit DotSlash invocation.
- Provision DotSlash in formatting CI and contributor devcontainers, and
document the source-build prerequisite.
- Apply the initial mechanical buildifier formatting baseline.
## Why
Managed deployments need a reliable deny gate for remote control.
Persisted enablement and explicit startup requests currently remain able
to start the transport, while the removed `features.remote_control` key
is intentionally only a compatibility no-op.
This adds a dedicated requirement that administrators can use to force
remote control off without deleting the user's persisted preference.
Removing the requirement and restarting restores the prior choice.
## What Changed
- Added top-level `allow_remote_control` requirements parsing, sourced
layer precedence, debug output, and `configRequirements/read` exposure
as `allowRemoteControl`.
- Added a typed transport policy captured from the startup requirements
snapshot. Managed disable forces the initial state to disabled and
prevents enrollment, refresh, connection, and persisted-preference
mutation.
- Rejected every `remoteControl/*` RPC before parameter deserialization
with JSON-RPC `-32600` and `remote control is disabled by managed
requirements`.
- Preserved the existing disabled status notification and the previous
behavior when the requirement is `true` or omitted.
- Regenerated app-server protocol schemas and documented the new
requirement.
## Verification
- Confirmed all remote-control RPCs, including a malformed request,
return the managed-policy error while the initial status notification
remains `disabled`.
- Confirmed explicit ephemeral startup and persisted enablement make no
backend connection and leave the SQLite preference unchanged.
- Confirmed `allow_remote_control = true` does not enable or block
remote control and `configRequirements/read` returns
`allowRemoteControl: false` for the deny policy.
Related issue: N/A (managed-policy hardening).
## Why
[#25345](https://github.com/openai/codex/pull/25345) was approved,
green, and squash-merged into its stacked base branch,
`fcoury/tokenmaxxing-api`. Four minutes later, that base branch was
force-pushed back to an API-only rebased head while preparing
[#25344](https://github.com/openai/codex/pull/25344) for `main`. As a
result, the squash commit from #25345 was orphaned and the TUI command
never reached `main` or a release.
This PR relands the orphaned TUI change from
[`411410b8`](https://github.com/openai/codex/commit/411410b85c2d8eb050d441f17396c5c4048d866f)
on current `main`.
## What changed
- Add `/usage`, `/usage daily`, `/usage weekly`, and `/usage cumulative`
for account token activity.
- Fetch account usage asynchronously through the existing
`account/usage/read` app-server RPC.
- Render daily, weekly, and cumulative activity with theme-aware
terminal palettes and bounded transient cards.
- Preserve transcript ordering while assistant streams, history
consolidations, active cells, and hooks complete.
- Hide `/usage` from completion when backend auth is unavailable while
keeping typed-command guidance.
- Carry current-main behavior forward for cwd-aware Markdown parsing,
Windows Terminal color detection, and personal access token auth.
- Clear pending usage cards on thread rollback and delay completed cards
until live hook output is committed.
- Add focused regression and snapshot coverage for loading, auth errors,
invalid views, rollback, hook ordering, layout, and charts.
## Prior review
The original implementation was approved by Eric Traut in #25345 after
testing multiple themes and light/dark terminals. This PR preserves that
reviewed implementation while adapting it to current `main` and adding
regression coverage for newer rollback and hook lifecycle behavior.
## Validation
- `just test -p codex-tui token_activity palette renderable
usage_command` — 37 passed.
- Focused rollback, hook-ordering, and error snapshot tests — 4 passed.
- `just fix -p codex-tui` — passed.
- `UV_CACHE_DIR=/private/tmp/codex-uv-cache just fmt` — passed.
- `cargo insta pending-snapshots` — no pending snapshots.
- `just test -p codex-tui` — 2,870 passed; two unrelated guardian
feature-flag tests failed because their expected `OverrideTurnContext`
event was absent:
-
`update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
-
`update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
- `just argument-comment-lint` could not complete because the local
Bazel LLVM `compiler-rt` repository is missing `include/sanitizer/*.h`.
The touched Rust diff was manually inspected and no missing
opaque-literal argument comments were found.
## Stack
1. [1 of 3] Support long raw TUI goal objectives - #27508
2. [2 of 3] Support long pasted text in TUI goals - #27509
3. **[3 of 3] Support images in TUI goals** - this PR
## Why
The first two PRs make goal definitions resilient to long text, but
`/goal` still dropped image inputs from the composer. That meant a user
could attach images while defining a goal and the resulting goal
continuation would not have any useful reference to those images.
Goal state still persists only objective text, so image inputs need to
become paths or URLs that the agent can read later.
## What Changed
- Extends TUI `GoalDraft` with local image attachments and remote image
URLs.
- Copies local goal images through the app-server filesystem layer into
the managed goal attachment directory, then rewrites active image
placeholders to file references.
- Appends unplaced local images and remote image URLs to the objective
as referenced image files or URLs.
- Preserves goal image metadata through live `/goal` submission and
queued `/goal` dispatch.
## Verification
- Added goal materialization coverage for local image files and remote
image URLs.
- Added/updated TUI slash-command coverage showing `/goal` drafts
include attached images instead of dropping them.
## Manual Testing
- Attached an image by bracketed-pasting its local path into a live
`/goal` composer. The `[Image #1]` placeholder became a server-host
`image-1.png` reference, copied bytes matched exactly, and no attachment
was written under the TUI's local home.
- Deleted an image placeholder before submitting a small goal and
verified no image was copied.
- Attached PNG and JPEG files to the same goal. Placeholder order was
preserved as `image-1.png` and `image-2.jpg`, and both remote copies
matched their source bytes.
- Tried extensionless, malformed-extension, and
extension/content-mismatched paths; the composer rejected them as image
attachments before goal dispatch rather than creating misleading managed
image files.
- Combined a local image, a large pasted block, and enough raw text to
exceed 4,000 characters. The remote attachment directory contained the
image, paste sidecar, and `goal-objective.md`; all embedded references
used server-host paths and both payloads matched their sources.
- Submitted an image replacement while a goal was active, verified no
image was copied before confirmation, then canceled and confirmed the
attachment count was unchanged.
## Summary
This PR promotes Mentions 2.0 (unified TUI mention popup) to stable and
enables it by default.
- Keep `mentions_v2` as a temporary rollback path to the legacy split
popups (`--disable mentions_v2`).
- Add feature-default and snapshot coverage for the default experience.
## Prior work
- [#19068 — Unified mentions in
TUI](https://github.com/openai/codex/pull/19068)
- [#22375 — Use plugin/list to get plugins for
mentions](https://github.com/openai/codex/pull/22375)
- [#23363 — Unified mentions tweaks and rendering
polish](https://github.com/openai/codex/pull/23363)
## Test plan
- Launch Codex without any feature overrides.
- Type `@` in the TUI composer.
- Confirm the unified mentions menu opens and displays filesystem,
plugin, and skill results.
## Stack
1. [1 of 3] Support long raw TUI goal objectives - #27508
2. **[2 of 3] Support long pasted text in TUI goals** - this PR
3. [3 of 3] Support images in TUI goals - #27510
## Why
Large text pasted into the TUI composer is represented as a paste
placeholder plus pending paste metadata. For `/goal`, preserving only
the visible placeholder is not enough: the agent would see a short
placeholder string instead of the actual pasted text, and the long-text
support from the first PR would never see the payload.
The TUI also needs to avoid writing stale sidecar files when a user
pastes a large block and then deletes its placeholder before submitting
the goal.
## What Changed
- Introduces a TUI `GoalDraft` for goal submissions so `/goal`, `/goal
edit`, and queued goal commands can carry objective text plus text
elements and pending paste payloads.
- Materializes active pasted-text placeholders to `pasted-text-N.txt`
files through the app-server filesystem path introduced in #27508.
- Rewrites active paste placeholders in the persisted objective to file
references, while leaving literal placeholder-looking text alone.
- Filters out deleted paste placeholders so otherwise-small goals do not
require `$CODEX_HOME` or remote filesystem writes.
- Preserves pending paste metadata when a `/goal` command is queued
before a thread exists.
## Verification
- Added goal materialization tests for active paste placeholders,
deleted paste placeholders, and whitespace-only paste payloads.
- Added/updated TUI slash-command tests for large pasted text, queued
`/goal` commands before thread start, and queued oversized goal
behavior.
## Manual Testing
- Used real terminal bracketed-paste sequences through a remote TUI
session. A 1,228-byte multiline paste became `pasted-text-1.txt`; its
first/last lines and byte count matched exactly, and the persisted
objective referenced the server-host path.
- Pasted a large block, deleted its placeholder, and submitted a small
replacement objective. No new directory or sidecar file was created.
- Added two same-length large pastes to one goal. The composer
disambiguated their visible placeholders, and materialization preserved
order and contents in `pasted-text-1.txt` and `pasted-text-2.txt`.
- Submitted a whitespace-only large paste and verified the goal was
rejected as empty without writing a file.
- Submitted a pasted-text replacement while another goal was active,
verified no file was written before confirmation, then canceled and
confirmed the original goal remained unchanged.
- Combined a large paste with enough raw text to exceed 4,000 characters
after placeholder rewriting. The paste sidecar and `goal-objective.md`
were created in the same remote attachment directory, and `/goal edit`
restored the rewritten objective with its sidecar reference.
## Why
Windows Credential Manager limits generic credential blobs to 2,560
bytes. Large serialized ChatGPT auth payloads can exceed that limit, so
keyring-mode CLI auth needs a backend that keeps only the encryption key
in the OS keyring and stores the payload in Codex's encrypted
local-secrets file.
This is the third PR in the encrypted-auth stack:
1. #27504 — feature and config selection
2. #27535 — auth-specific local-secrets namespaces
3. This PR — CLI auth implementation and activation
4. MCP OAuth implementation and activation
## What Changed
- Added encrypted CLI-auth storage using the `CliAuth` secrets
namespace.
- Preserved direct keyring storage for platforms/configurations where it
remains selected.
- Selected the backend consistently for login, logout, refresh,
device-code login, auth loading, and login restrictions.
- Threaded resolved bootstrap/full config through CLI, exec, TUI,
app-server account handling, cloud config, and cloud tasks.
- Removed stale `auth.json` fallback data after successful encrypted
saves and removed encrypted, direct-keyring, and fallback data during
logout.
- Added storage and integration coverage for both direct and encrypted
keyring modes.
MCP OAuth persistence is intentionally left to the next PR.
## Validation
- `just test -p codex-login` — 131 passed
- `just test -p codex-cli` — 280 passed
- `just test -p codex-app-server v2::account` — 25 passed
- `just test -p codex-cloud-config service` — 21 passed, 7 skipped
- `just fix -p codex-login`
- `just fix -p codex-cli`
- `just fmt`
## Why
Removes the realtime audio support from TUI.
## What Changed
- Removed the TUI `/realtime` and realtime `/settings` command paths.
- Deleted TUI voice capture/playback, WebRTC session handling,
audio-device selection UI, and recording-meter code.
- Removed TUI realtime tests and snapshots that covered the deleted
surfaces.
- Dropped the TUI-only `cpal` and `codex-realtime-webrtc` dependencies
and refreshed the Rust/Bazel locks.
## Why
Standalone image generation emitted a started item but no terminal item
when the backend failed. Clients could leave the operation unresolved or
render it as successful.
## What changed
- Emit a terminal image-generation item with `status: "failed"` when
generation or editing fails.
- Skip image persistence for failed terminal items.
- Render failed image generation distinctly in TUI history.
- Preserve the status when handling live and replayed terminal items.
## Looks for TUI, App-Side change needed
<img width="867" height="89" alt="image"
src="https://github.com/user-attachments/assets/9e32342f-a982-411e-8498-456639fc468a"
/>
## Validation
- `just test -p codex-image-generation-extension`
- App-server image-generation tests
- Core stream-event tests
- TUI image-generation lifecycle and snapshot tests
- Scoped Clippy and formatting
## Summary
- expose the remote plugin detail endpoint's `share_url` as nullable
`PluginDetail.shareUrl`
- preserve existing `PluginSummary.shareContext` behavior for local and
workspace sharing flows
- regenerate the app-server TypeScript and JSON schema fixtures
## Why
The remote plugin detail response already includes a canonical
`share_url`, but that value was not surfaced by `plugin/read` for global
plugins. Global plugins intentionally have no `shareContext`, so using
that model for the URL would change the semantics consumed by the
existing share modal.
## User impact
Codex clients can use `PluginDetail.shareUrl` for a remote plugin's
copy-link action, including when the plugin is disabled by an
administrator, without changing existing share-modal or ownership
behavior.
## Validation
- `cargo test -p codex-app-server
plugin_read_includes_share_url_for_admin_disabled_remote_plugin`
- `cargo test -p codex-app-server-protocol
typescript_schema_fixtures_match_generated`
- `cargo test -p codex-app-server-protocol
json_schema_fixtures_match_generated`
- `cargo fmt --all`
## Why
The TUI rejected playful `/goal` spellings such as `/goooooooooooal`,
even though Codex Apps accepts them for the World Cup promotion. This
keeps the TUI behavior consistent without changing how the canonical
command is presented.
## How it works
Built-in command lookup recognizes lowercase `go+al` as the existing
`goal` command after normal exact-name parsing fails. The command
catalog remains unchanged, so autocomplete continues to advertise
`/goal` normally.
## Verification
Added lookup-level and end-to-end TUI coverage for the flexible
spelling. The focused tests, scoped Clippy checks, and formatting pass.
The full `codex-tui` suite passed 2,833 of 2,835 tests; the two failing
guardian feature-flag tests reproduce unchanged on fresh `origin/main`.
## Summary
Choosing “Don’t remind me” can silently fail when `version.json`
disappears before dismissal because `dismiss_version` returns success
without writing anything. The same update can then reappear on the next
launch.
Initialize a minimal `VersionInfo` from the selected version when the
cache cannot be read, then persist the dismissal through the existing
write path.
Fixes#27147
## Why
Fixes#27735.
Side conversations are ephemeral forks, and thread rollback currently
requires persisted thread history. The normal backtrack path already
rejected editing previous prompts in side conversations, but
transcript-mode backtrack could still call the rollback path and surface
the core `thread/rollback` failure as a TUI error.
## What changed
- Moved the existing side-conversation edit rejection message into
`app_backtrack.rs` so backtrack rollback code can reuse it.
- Added a side-conversation guard in `apply_backtrack_rollback` so
transcript-mode confirmation is rejected before submitting
`thread/rollback`.
## Verification
- `just test -p codex-tui
app::tests::side_backtrack_rejection_reports_unavailable_message_snapshot`
## Summary
- restore source-specific wording across the `/import` picker, lifecycle
messages, diagnostics, and help text
- update the matching Unix and Windows snapshots
- leave import behavior unchanged
## Why
The import path currently supports one source, so the UI should identify
that source directly instead of presenting the flow as
provider-agnostic.
## Validation
- `just test -p codex-tui external_agent_config_migration` (12 passed)
- `just fix -p codex-tui`
- `just fmt`
## Why
`request_user_input` is moving beyond its original plan-mode-only
workflow, and future default/goal-mode usage needs a way for the model
to ask helpful but non-blocking questions without forcing the turn to
wait forever. This PR adds an explicit `autoResolutionMs` contract so a
later client/runtime change can auto-resolve unanswered prompts after a
bounded window while leaving truly blocking questions unchanged.
This is contract plumbing only; it does not implement the client-side
timer or auto-selection behavior, and the model-facing description
treats the field as reserved unless the current runtime explicitly
supports auto-resolution.
## What Changed
- Added optional `autoResolutionMs` to the model-facing
`request_user_input` args and core `RequestUserInputEvent`.
- Added model-facing schema text for `autoResolutionMs` while marking it
reserved for runtimes that explicitly support auto-resolution.
- Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
normalization by clamping out-of-range model-provided values.
- Propagated the field through app-server v2
`ToolRequestUserInputParams`, app-server request forwarding, generated
TypeScript, and JSON schema fixtures.
- Updated app-server, core, protocol, and TUI call sites/tests so
omitted values preserve existing `None`/`null` behavior and coverage
verifies a `Some(60_000)` round trip.
## Verification
- `just test -p codex-app-server-protocol`
- `just test -p codex-core request_user_input`
- `just test -p codex-app-server request_user_input_round_trip`
- `just test -p codex-tui request_user_input`
- `just test -p codex-protocol`
## Stack
1. **[1 of 3] Support long raw TUI goal objectives** - this PR
2. [2 of 3] Support long pasted text in TUI goals - #27509
3. [3 of 3] Support images in TUI goals - #27510
## Why
`thread/goal/set` limits persisted objective text to 4000 characters.
The TUI used to reject raw `/goal` objectives above that limit, even
though the client can make them usable by writing the long text to a
file and storing a short objective that points at that file.
This also needs to work for remote app-server sessions: filesystem API
calls must create files on the app-server host, and the stored path must
be meaningful to the agent on that host.
## What Changed
- Adds an app-server-host path helper so TUI code can build paths that
are resolved on the app-server host rather than the TUI host.
- Adds TUI app-server session helpers for `fs/createDirectory`,
`fs/writeFile`, `fs/readFile`, and `fs/remove` that work for embedded
and remote app-server sessions without changing the app-server protocol.
- Materializes oversized raw `/goal` objectives into
`$CODEX_HOME/attachments/<uuid>/goal-objective.md` through the
app-server filesystem APIs, then stores a short, readable objective that
directs the agent to that file.
- Reads managed objective files back for `/goal edit`. Other goal UI
renders the readable stored objective normally, without
managed-file-specific presentation logic.
- Recognizes managed references only when they name the expected
generated file under the app server's reported `$CODEX_HOME`, and cleans
up newly materialized files when goal replacement or setting does not
complete.
## Verification
- Added/updated TUI tests for raw oversized `/goal` submission, large
inline-paste expansion, queued oversized goals, app-facing
materialization before `thread/goal/set`, managed-path validation,
editing, and cleanup.
- Added/updated app-server-client remote coverage for initialized remote
Codex home handling.
## Manual Testing
- Ran the real TUI against a Unix-socket app server with different local
and server `$CODEX_HOME` directories. Oversized goals wrote only under
the server home, and persisted references used the server-canonical path
rather than the TUI path.
- Exercised 3,999-, 4,000-, and 4,001-character raw objectives. The
first two stayed inline without new files; the 4,001-character objective
became a managed objective file.
- Submitted a larger 8,275-character objective, verified its full
contents on the app-server host, and observed the goal continuation open
the referenced server-side file.
- Opened `/goal edit` for a managed objective and verified the full text
was restored through remote `fs/readFile`.
- Submitted an oversized replacement while a goal was active, verified
no file was written before confirmation, then canceled and confirmed
that the existing goal and attachment count were unchanged.
## Why
First-party async traits should expose their `Send` contracts explicitly
without requiring `async_trait`. This completes the migration pattern
established in #27303 and #27304.
## What changed
- Replaced the remaining first-party `async_trait` traits with native
return-position `impl Future + Send` where statically dispatched and
explicit boxed `Send` futures where object safety is required.
- Kept implementations behavior-preserving, outlining existing async
bodies into inherent methods where that keeps the diff reviewable.
- Removed all direct first-party `async-trait` dependencies and the
workspace dependency declaration.
- Added a cargo-deny policy that permits `async-trait` only through the
remaining transitive wrapper crates.
- Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
keep the full cargo-deny check passing.
## Validation
- `just test -p codex-exec-server`: 216 passed, 2 skipped.
- `just test -p codex-model-provider`: 39 passed.
- `just test -p codex-core` and `just test`: changed tests passed;
remaining failures are environment-sensitive suites unrelated to this
migration.
- `cargo deny check`
- `just fix`
- `just fmt`
- `cargo shear`
- `just bazel-lock-check`
Fixes#27210.
## Why
When the app server reports a visible `HookStarted` event for a
`PostToolUse` hook but the turn reaches `TurnCompleted` before a
matching hook completion event arrives, the TUI can leave the transient
`Running PostToolUse hook` row visible after the agent is done.
Interrupted and failed turn cleanup already drops transient live hook
rows; the normal completion path did not.
## What Changed
- Added `ChatWidget::clear_active_hook_cell()` for dropping transient
live hook status without writing it to history.
- Call that cleanup from normal task completion, while reusing it for
the existing start/finalize cleanup paths.
- Added `completed_turn_clears_visible_running_hook` snapshot coverage
for the reported `PostToolUse` case.
## Tests
- `just test -p codex-tui completed_turn_clears_visible_running_hook`
- `just test -p codex-tui` (fails on current `main` in unrelated
guardian tests:
`update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
and
`update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`)
## Why
We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
make embedders responsible for supplying user-level instructions. This
also ensures user instructions load when no primary environment is
selected.
## What changed
Stacked on #27415, which makes `codex exec` surface thread-scoped
runtime warnings.
- Added `UserInstructionsProvider` to `codex-extension-api`, with
absolute source attribution and recoverable loading warnings.
- Added `codex-home` with the filesystem-backed provider for
`AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
trimming, lossy UTF-8 handling, and the existing uncapped global
instruction size.
- Removed global instruction loading from `Config` and require
`ThreadManager` callers to inject a provider.
- Load provider instructions once for each fresh root runtime, including
runtimes without a primary environment. Running sessions retain their
snapshot, while child agents inherit the parent snapshot without
invoking the provider.
- Keep provider instructions separate while loading project `AGENTS.md`,
then assemble the model-visible instructions with the existing ordering,
source attribution, warning, and turn-context behavior.
- Wired the Codex home provider through the CLI, app server, MCP server,
core facade, and thread-manager sample.
## Validation
- `just test -p codex-home -p codex-extension-api`
- `just test -p codex-core agents_md`
- `just test -p codex-core guardian`
- `just test -p codex-app-server
thread_start_without_selected_environment_includes_only_global_instruction_source`
- `just test -p codex-exec warning`
- `just bazel-lock-check`
## What
- Parse optional `.app.json` `category` overrides for plugin apps.
- Add nullable `category` to `AppSummary` and `AppTemplateSummary` in
the app-server protocol.
- Fall back from `branding.category` to the first non-empty
`app_metadata.categories` value when building app/template summaries.
- Regenerate schema/type fixtures and update plugin read/install tests.
## Why
The plugin details UI needs a normalized per-app category. Some apps
only provide their default category in metadata, while others need a
local `.app.json` override.
## Summary
TUI exits printed the resume/session summary only after checking the
exit reason. On fatal exits, both CLI wrappers wrote the error and
called `process::exit(1)` immediately, so an active session that ended
on a fatal error could skip the session information entirely.
This change prints the normal exit summary before returning the fatal
nonzero exit code. If a fatal exit has a known thread id but no
resumable rollout hint, it prints `Session ID: <id>` instead of staying
silent. It also flushes stdout before `process::exit(1)` so the summary
line is not lost during process teardown.
## Implementation
- Apply the fatal-exit ordering fix in both `codex` and standalone
`codex-tui`.
- Keep normal user-requested exit behavior unchanged.
- Preserve the existing resume hint when a rollout is resumable, and use
the raw thread id only as a fatal-exit fallback.
## Why
This is part of an ongoing attempt to eliminate the TUI's direct
dependency on core features. When we moved the TUI to the app server, we
left a `legacy_core` shim that re-exported some remaining core symbols
for the TUI. The intent was to eventually remove all of these.
In this PR, we remove the symbols related to the Windows sandbox.
The change should be behavior-neutral and low risk because it's just
refactoring and removal of code that is now effectively dead.
When working on this PR, I noticed a big existing problem that affects
mixed-platform remoting. For example, if you run the TUI on a Linux box
and remote into a Windows box, the TUI logic doesn't properly handle
Windows sandbox setup properly. Fixing this is beyond the scope of this
PR, but I've left a TODO comment in place so we don't forget.
## What changed
- Move the remaining TUI-specific sandbox level, setup, telemetry, and
read-root helpers into `codex-tui`, calling `codex-windows-sandbox`
directly.
- Remove the Windows sandbox namespace and read-root grant re-exports
from the client-side `legacy_core` facade.
- Remove the dormant pre-elevation prompt fallback guarded by the
permanently enabled `ELEVATED_SANDBOX_NUX_ENABLED` switch. The reachable
elevated and non-elevated setup flows remain unchanged.
## Why
Codex needs to manage Amazon Bedrock API key credentials through the
existing auth lifecycle instead of introducing a separate auth manager
or provider-specific credential file. Treating Bedrock API key login as
a primary auth mode gives it the same persistence, keyring, reload, and
logout behavior as the existing OpenAI API key and ChatGPT modes.
The credential is valid only for the `amazon-bedrock` model provider.
OpenAI-compatible providers must reject this auth mode rather than
treating the Bedrock key as an OpenAI bearer token.
## What changed
- Added `bedrockApiKey` as an app-server `AuthMode` and
`CodexAuth::BedrockApiKey` as a primary `AuthManager` mode.
- Added `BedrockApiKeyAuth`, containing the API key and AWS region, to
the existing `AuthDotJson` payload stored in `$CODEX_HOME/auth.json` or
the configured keyring backend.
- Added `login_with_bedrock_api_key(...)`, parallel to
`login_with_api_key(...)`, which replaces the current stored login with
Bedrock credentials.
- Reused generic auth reload and logout behavior instead of adding a
Bedrock-specific auth manager or logout path.
- Updated login restrictions, status reporting, diagnostics, telemetry
classification, generated app-server schemas, and auth fixtures for the
new mode.
- Added explicit errors when Bedrock API key auth is selected with an
OpenAI-compatible model provider.
This PR establishes managed storage and auth-mode behavior. Routing the
managed key and region into Amazon Bedrock requests will be in follow-up
PRs.
## Why
The TUI still reached through `codex-app-server-client::legacy_core` for
process telemetry setup and personality migration, exposing core-only
details after the TUI moved onto the app-server layer.
This is part of our ongoing efforts to whittle away at the legacy_core
shim that was left over after migrating the TUI to the app server.
This change is just a refactor/rename and should be behavior-neutral and
low risk.
## What changed
- expose OTEL provider construction through the app-server client and
keep the small process/SQLite telemetry adapters local to the TUI
- collapse personality migration results to the config-reload decision
the TUI needs
- remove the `legacy_core::otel_init` and
`legacy_core::personality_migration` subnamespaces
## Summary
The app server exposes `thread/delete`, but users cannot invoke it from
the CLI or TUI. Because deletion is irreversible, the user-facing
commands need deliberate confirmation and safer handling of name-based
targets.
- Add `codex delete <SESSION>` with interactive confirmation,
restricting `--force` to UUID targets.
- Resolve exact names across active and archived sessions, including
renamed sessions, and validate prompted UUID targets before
confirmation.
- Add a `/delete` command with a confirmation popup that warns the
current session and its subagent threads will be permanently deleted.
## Manual testing
- Deleted by UUID with `--force` and verified the rollout, session-index
entry, and database row were removed.
- Exercised name-based confirmation for both cancellation and
affirmative deletion; cancellation preserved the session and
confirmation removed it.
- Verified deletion refuses to proceed without `--force`, while
`--force` rejects names, including duplicate names.
- Verified duplicate-name confirmation displays the concrete UUID
selected.
- Deleted an archived session by name.
- Verified an already-missing UUID fails before displaying a
confirmation prompt.
- Exercised `/delete` in the TUI: the popup defaults to No, cancellation
preserves the session, and confirmation deletes the session and exits.
- Verified that `codex delete` works for both archived and non-archived
sessions.
## Why
The TUI now sits on the app-server layer, but
`app-server-client::legacy_core` still exposed core test helpers solely
for TUI tests. We've been whittling away the remaining dependencies.
This is the next step on that journey.
There is no functional change — just a refactor, and this affects only
test code, so it should be low risk.
## What changed
- remove the `legacy_core::test_support` re-export and call
model-manager test helpers directly
- keep the bundled model-preset cache local to TUI test support
- import constraint types directly from `codex-config`
## Summary
- remove the redundant `needsAuth` field from `AppSummary` and generated
app-server schemas
- stop `plugin/read` from querying Apps MCP solely to hydrate unused
connector auth state
- preserve `plugin/install.appsNeedingAuth` membership and
`app/list.isAccessible` as the authentication signals
## Why
Codex App and TUI do not consume `plugin/read.plugin.apps[].needsAuth`.
Hydrating it could establish an Apps MCP connection and discover tools
on a cold `plugin/read` request, adding avoidable latency. The plugin
APIs are still marked under development, so removing this wire field is
preferable to retaining a misleading default.
## Verification
- `just write-app-server-schema`
- `just fmt`
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server
plugin_install_uses_remote_apps_needing_auth_response`
- `just test -p codex-app-server
plugin_install_returns_apps_needing_auth`
- `just test -p codex-app-server
plugin_read_returns_plugin_details_with_bundle_contents`
- `just test -p codex-tui
plugin_detail_popup_snapshot_shows_install_actions_and_capability_summaries`
- `$xin-build` simplify and debug reviews
## Why
External-agent import should be discoverable and deliberate without
blocking startup or claiming the public `codex [PROMPT]` CLI namespace.
The slash command keeps the flow local to the interactive TUI and reuses
the existing app-server import API.
## What changed
- add the user-facing `/import` slash command
- detect external-agent importable items only when the command is
invoked
- run imports through the embedded local app-server
- show start and completion messages, refresh configuration, and block
duplicate imports while one is pending
- reject the flow for unsupported remote and local-daemon sessions
## Validation
- `just test -p codex-tui external_agent_config_migration` (10 passed)
- manually exercised an isolated TUI fixture with existing
external-agent setup and session data using a fresh `CODEX_HOME`
- verified picker customization, plugin and session detection, import
completion, repeated invocation, and imported-session resume context
- the broader `just test -p codex-tui` run passed 2,805 tests, with 2
unrelated guardian feature-flag failures and 4 skipped tests
## Draft follow-ups
- review whether completion messaging should remain attached to the
initiating chat if the user switches chats during an import
- review shutdown semantics for an in-progress background import
## Stack
1. [#27064](https://github.com/openai/codex/pull/27064): remove the
startup migration flow
2. [#27065](https://github.com/openai/codex/pull/27065): extract the
picker renderer
3. [#27070](https://github.com/openai/codex/pull/27070): add the
external-agent import picker UX
4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
through `/import`
**This PR is stack item 4.** Draft while the lower stack dependencies
are reviewed.
## Why
Users need to understand what external-agent data Codex detected, what
is selected, and how to proceed before an import begins. The updated
picker makes focus, selection state, and the submission path explicit
while preserving the existing import backend.
## What changed
- replace the old migration prompt with a two-step external-agent import
picker
- add a customize view with explicit item focus, selection state,
counts, and a review action
- separate detected import data into a view model
- add Unix and Windows snapshots for prompt, item-focus, and
action-focus states
## Validation
- `just test -p codex-tui external_agent_config_migration` (10 passed)
- manually exercised an isolated TUI fixture covering customization,
selection toggles, review, import, repeated invocation, and session
resume
- the broader `just test -p codex-tui` run passed 2,805 tests, with 2
unrelated guardian feature-flag failures and 4 skipped tests
## Review note
This is the largest layer in the stack because the interaction state,
rendering changes, and required snapshots move together. It remains a
draft in case reviewers prefer a further presentation/state split.
## Stack
1. [#27064](https://github.com/openai/codex/pull/27064): remove the
startup migration flow
2. [#27065](https://github.com/openai/codex/pull/27065): extract the
picker renderer
3. [#27070](https://github.com/openai/codex/pull/27070): add the
external-agent import picker UX
4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
through `/import`
**This PR is stack item 3.** Draft while the lower stack dependencies
are reviewed.
## Why
The external-agent import picker is easier to review when its rendering
refactor lands separately from new state and interaction behavior. This
layer is intended to be behavior-neutral.
## What changed
- extract external-agent migration rendering into a dedicated `render`
module
- preserve existing behavior while separating presentation from
interaction logic
- establish a smaller foundation for the import picker UX in the next PR
## Validation
- `just test -p codex-tui external_agent_config_migration` (10 passed)
## Stack
1. [#27064](https://github.com/openai/codex/pull/27064): remove the
startup migration flow
2. [#27065](https://github.com/openai/codex/pull/27065): extract the
picker renderer
3. [#27070](https://github.com/openai/codex/pull/27070): add the
external-agent import picker UX
4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
through `/import`
**This PR is stack item 2.** Draft while the lower stack dependency is
reviewed.
## Why
External-agent import should be initiated deliberately instead of
interrupting eligible TUI startups. This cleanup removes the blocking
startup flow before the replacement import experience is introduced
later in the stack.
## What changed
- remove the startup-blocking external-agent migration prompt
- remove the now-unused external migration feature gate
- remove the obsolete TUI app-server migration wrappers
- retain the dormant picker behind a module-scoped dead-code allowance
until the next stack item wires it back in
- keep normal TUI startup focused on entering Codex immediately
## Validation
- `bazel build --config=clippy //codex-rs/tui:tui
//codex-rs/tui:tui-unit-tests-bin`
- `just test -p codex-tui external_agent_config_migration` (8 passed)
- `just test -p codex-tui` (2,786 passed, 12 unrelated local
environment-sensitive failures, 4 skipped)
- `just fix -p codex-tui`
- `just fmt`
## Stack
1. [#27064](https://github.com/openai/codex/pull/27064): remove the
startup migration flow
2. [#27065](https://github.com/openai/codex/pull/27065): extract the
picker renderer
3. [#27070](https://github.com/openai/codex/pull/27070): add the
external-agent import picker UX
4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
through `/import`
**This PR is stack item 1.**
Further investigation of the sqlite incidents showed that the problems
are due to corruption from the older version of SQLite that we recently
upgraded, and that the data is truly corrupted in the root database --
recovery of all data is not possible. Given that the data is
reconstructable from the rollouts on disk, we should just auto-backup
the database and let codex rebuild the rollout info from the disk
rollouts.
The new behavior is that appserver auto-backs-up and rebuilds (with logs
reflecting that behavior). The CLI now pops a message letting you know
this happened and the paths of the backed-up corrupt db and the new
database. There is also context added so that the desktop app can read
the rebuild info from it and inform the user with it.
## Why
Clients can archive and unarchive threads today, but there is no
app-server API for permanently removing a thread. Deletion also needs to
cover the full session tree: deleting a main thread should remove
spawned subagent threads and the related local metadata instead of
leaving orphaned rollout files, goals, or subagent state behind.
## What
- Adds the v2 `thread/delete` request and `thread/deleted` notification,
with the response shape kept consistent with `thread/archive`.
- Implements local hard delete for active and archived rollout files.
- Deletes the requested thread's state DB row as the commit point, then
best-effort cleans associated state including spawned descendants,
goals, spawn edges, logs, dynamic tools, and agent job assignments.
- Updates app-server API docs and generated protocol schema/TypeScript
fixtures.
## Why
MCP startup status notifications are thread-owned, but `ChatWidget`
trusted upstream routing. If routing state delivered a tagged child
notification to the active parent widget, the child MCP failure could
still mutate the parent's startup state and transcript. Rejecting it
only inside the MCP handler was also too late because shared
notification handling could already restore and consume the parent's
retry status.
## What changed
- Validate a tagged MCP status notification against the visible
`ChatWidget` thread before shared notification handling mutates any
parent state.
- Cover child `Starting` and `Failed` notifications delivered to a
retrying parent widget, asserting that they preserve its visible retry
error and saved status header while producing no history or MCP status
mutation.
## User impact
Subagent MCP startup failures remain scoped to the child transcript
instead of appearing as duplicate warnings in the parent transcript.
## Testing
- `just test -p codex-tui mcp_startup_ignores_status_for_other_thread`
- `just test -p codex-tui
primary_thread_ignores_child_mcp_startup_notifications`
- `just fmt`
## Why
The TUI still reached through `app-server-client::legacy_core` for
thread-name normalization and project-instruction filename details. In
particular, checking the TUI's local filesystem for `/init` is incorrect
for remote app-server sessions, where the server owns the working
directory and instruction discovery.
## What changed
- use the instruction source paths supplied by the app server to decide
whether `/init` should avoid overwriting project instructions
- keep the small thread-name normalization helper local to the TUI
- remove the now-unused instruction filename constants, utility module,
and other unused `legacy_core` re-exports
- make status helper tests independent of concrete instruction filenames
## Verification
- `just test -p codex-app-server-client`
- `just test -p codex-tui
slash_init_skips_when_project_instructions_are_loaded`
- `just test -p codex-tui` ran 2,799 tests; 2,797 passed and two
unrelated guardian feature-flag tests failed reproducibly in untouched
code
### Manual test
Started an app server over WebSocket with a remote workspace containing
`AGENTS.md`, then connected the TUI using `--remote`. After confirming
`thread/start` returned the file in `instructionSources`, deleted
`AGENTS.md` and ran `/init` in the existing session.
The TUI still reported that project instructions already existed and
skipped `/init`. The trace contained no `turn/start` request, confirming
the decision came from app-server session state rather than a new
client-local filesystem check.
This changes the `/review` escape path so `Esc` no longer behaves like
the normal queued-follow-up interrupt flow while a review is running.
Steering is not currently supported in `/review` mode, without this
change users are able to attempt a steer but it leads to a crash (see
#22815). If the user has already tried to send additional guidance
during `/review`, the TUI now keeps the review running and shows a
warning that steer messages are not supported in that mode, while still
pointing users to `Ctrl+C` if they actually want to cancel. It also adds
regression coverage for the review-specific warning behavior. When users
do cancel with Ctrl+C during /review, the TUI now tolerates the
active-turn race that can happen during review handoff, and any queued
steer messages are restored to the composer instead of being discarded.
- Special-case `Esc` during an active `/review` when follow-up steer
input is pending or has already been deferred.
- Show a clear warning instead of interrupting the running review.
- Make the Ctrl+C cancel path during /review resilient to active-turn
races, while preserving any queued steer text by restoring it to the
composer.
- Add review-mode test coverage for the warning path.
## Testing
1. Start a `/review` with a diff large enough that the review stays
active for more than a few seconds.
2. While the review is still running, type a follow-up / steer message,
submit it, and then press `Esc`.
Before: `Esc` causes the TUI to close abruptly.
After: the review keeps running and the transcript shows a warning that
steer messages are not supported during `/review`, with guidance to use
`Ctrl+C` if you want to cancel.
3. Press `Ctrl+C` if you actually want to stop the review.
Before: (after restarting the test since Pt. 2 crashed) this is the
intentional cancellation path.
After: this remains the intentional cancellation path, and any queued
follow-up steer text is restored to the composer instead of being lost.
## Note:
`/review` mode explicitly does not support steering at this time (as
noted in `turn_processer.rs`, if we want to explore that in the future
this code will need to be modified). This change keeps unsupported steer
attempts from crashing the TUI and preserves queued follow-up text if
the user cancels with Ctrl+C.
## Why
Multi-agent v2 identifies agents by canonical paths, but its tool
handlers still emitted the larger legacy collaboration begin/end events
built around nickname and role metadata. App-server, rollout-trace,
analytics, and TUI consumers therefore lacked one compact path-based
completion signal that behaved consistently across live events and
replay.
The TUI also needs a bounded `/agent` status surface for v2 agents. It
should use recent local activity for previews, refresh liveness without
loading full histories, and keep the legacy picker available when no
path-backed v2 agent is known.
## What changed
- Replace the v2 `spawn_agent`, `send_message`, `followup_task`, and
`interrupt_agent` legacy lifecycle emissions with a success-only
`SubAgentActivity` event. The event records the tool call ID, occurrence
time, affected thread, canonical agent path, and `started`,
`interacted`, or `interrupted` kind.
- Expose the activity as a completion-only app-server v2
`subAgentActivity` thread item in live notifications and reconstructed
history, regenerate the protocol schemas, and count it in sub-agent tool
analytics.
- Track canonical paths from live activity and loaded-thread metadata in
the TUI, and render the activity in live and replayed transcripts.
- Make `/agent` list running path-backed agents with summaries from
bounded local event buffers. Each summary is capped at 240 graphemes,
the scan is capped at six recent items, only the last three wrapped
lines are shown, and command output is omitted. Liveness falls back to
metadata-only `thread/read` when local turn state is unavailable.
- Persist the activity as a terminal rollout-trace runtime payload and
reduce it to the corresponding spawn, send, follow-up, or close
interaction edge. `interrupt_agent` is classified as a close-edge
operation.
- Preserve the legacy picker when no path-backed v2 agent is known.
## Compatibility
App-server v2 clients that consumed `collabAgentToolCall` begin/end
pairs for these tools must handle the new completion-only
`subAgentActivity` item. Legacy v1 collaboration behavior is unchanged.
## Screenshot
<img width="684" height="288" alt="Screenshot 2026-06-08 at 15 40 47"
src="https://github.com/user-attachments/assets/194b3cd0-619d-45fb-b587-cf3e2b1b8a1d"
/>
## Testing
- `just test -p codex-app-server-protocol`
- `just test -p codex-rollout-trace`
- Added focused coverage for activity analytics, terminal trace
serialization, spawn-edge reduction, `interrupt_agent` classification,
TUI status rendering without aggregated command output, and clearing
stale running state after a completed turn.
Codex forces `core.fsmonitor=false` on internal Git commands so a
repository cannot select an executable fsmonitor helper. This also
disables Git's built-in daemon for `status`, `diff`, and `ls-files`,
turning those worktree reads into full scans in large repositories.
Read the raw effective `core.fsmonitor` value and preserve it only when
Git interprets it as true and advertises built-in daemon support through
`git version --build-options`. Query uncommon boolean spellings back
through Git using the exact effective value. Unset, false, helper paths,
malformed values, probe failures, and unsupported Git builds continue to
force `core.fsmonitor=false`.
Centralize this policy in `git-utils` while keeping process execution in
the existing local and workspace-command adapters. Probe once per
worktree workflow and reuse the result for its Git commands, including
the TUI `/diff` path. Metadata-only commands and repository discovery
remain disabled without probing. Each probe and requested Git process
keeps its own existing timeout, and the decision is not cached because
layered and conditional Git configuration can change while Codex runs.
---------
Co-authored-by: Chris Bookholt <bookholt@openai.com>
Fixes a TUI regression where thread transitions such as `/new` and
`/clear` could rebuild config without the cloud requirements loader,
allowing users to fall back to non-cloud-managed settings. The config
refresh path now preserves cloud requirements during thread
reinitialization, and config loading is moved off the deep TUI event
stack to avoid stack-overflow crashes during those reloads.
- Passes the cloud requirements loader through TUI config rebuild paths.
- Keeps cloud requirements applied for `/new`, `/clear`, `/fork`, side
conversations, and session picker transitions.
- Runs config building on a Tokio task so reloads do not occur on the
deep TUI caller stack.
- Adds regression coverage that cloud requirements survive
thread-transition config refreshes.
## Test/Repro:
- Start Codex with a cloud requirement applied.
- Use `/new` or `/clear`.
- The refreshed/fresh-session config should still include the cloud
requirements
This can be tested with any config item, at this moment for oai staff
the easiest item to test is the `mentions_v2` feature. This is currently
enabled in cloud requirements, but is not enabled by default. As a
result, prior to these changes that feature is disabled after `/new` or
`/clear`. Testing the same steps with a binary from this branch should
not drop the feature enablement.
## Summary
- Render `/debug-config`'s `allowed_sandbox_modes` from the finalized
permission constraints instead of the raw requirements list.
- Add regression coverage for configured full-access and external
sandbox modes being omitted when effective permissions reject them.
## Details
`allowed_sandbox_modes` comes from managed requirements, but the final
permissions can be further constrained by derived validation rules. For
example, `permissions.filesystem.deny_read` requires sandbox
enforcement, so modes that disable or externalize Codex's sandbox are
not actually usable even if they were present in the raw requirements
TOML.
The debug renderer now enumerates the configured sandbox-mode labels and
keeps only those accepted by `Config.permissions`. That makes
`/debug-config` reflect the same effective permission-profile constraint
path used by runtime config validation, while preserving the existing
source/provenance display.
## Validation
- Added a regression test for effective sandbox-mode filtering in
`/debug-config`.
## Background
Bare URLs containing `~` in their path are currently only clickable up
to the tilde in the interactive TUI. For example, Codex renders the
visible text for:
`https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
but the OSC 8 destination stops at `https://www.cs.tufts.edu/`. This
makes Cmd-click open the wrong location even though the terminal
recognizes the complete URL outside Codex.
Fixes#26774.
## Root Cause
The URL scanner already accepts `~`. The truncation happens earlier:
with strikethrough parsing enabled, `pulldown-cmark` splits this URL
into adjacent decoded `Event::Text` values around the tilde. The
Markdown renderer annotated each text event independently, so only the
first event still looked like a complete URL with a supported scheme.
The renderer now merges adjacent decoded text events before URL
annotation. It preserves the combined source range while retaining
parser-decoded contents, which avoids regressing entities such as
`&`.
## Changes
- Add a small iterator that merges adjacent decoded Markdown text events
and their source ranges.
- Apply it at the Markdown renderer boundary before hyperlink detection.
- Add regression coverage for the reported URL in prose, wrapped table
output, and entity-decoded URLs.
## How to Test
1. Run Codex with `just c`.
2. Ask the assistant to output this exact bare URL with no Markdown link
syntax:
`https://www.cs.tufts.edu/~nr/cs257/archive/olin-shivers/dissertation.pdf`
3. Hold Cmd and hover or click the URL.
4. Confirm the complete URL, including the suffix after `~`, is one
destination.
5. Repeat with the URL inside a Markdown table and confirm wrapped
portions retain the same complete destination.
Targeted tests:
- `just test -p codex-tui url_with_tilde`
- `just test -p codex-tui merged_text_events_preserve_entity_decoding`
The full `codex-tui` test run was also executed. Its only failures were
the two existing Guardian feature-flag tests:
-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
## Why
Auto Review should remain the effective approval reviewer when settings
cross runtime boundaries. A config or app-server round trip must not
change the reviewer identity, and delegated work must not silently fall
back to user review.
This requires both a stable canonical serialized value and propagation
of the effective setting. `auto_review` is the canonical value across
protocol and app-server output, while `guardian_subagent` remains
accepted as backward-compatible input.
## What changed
- serialize `ApprovalsReviewer::AutoReview` consistently as
`auto_review` across core protocol and app-server v2
- continue accepting `guardian_subagent` when reading existing config or
client requests
- carry the active turn's approval reviewer into spawned agents
- update config/debug expectations and add delegated-task regression
coverage
## Scope
This does not change Guardian policy or remove compatibility with
existing `guardian_subagent` inputs. It preserves the selected reviewer
across serialization, config reloads, app-server settings, and delegated
task setup.
Related Guardian changes are split independently:
- #26231 adds denials and soft denials
- #26334 retries transient reviewer failures
- #26333 reuses narrowly scoped low-risk approvals
- #26232 adds TUI denial recovery
## Validation
- `just test -p codex-app-server-protocol` (224 passed)
- regression coverage for delegated task reviewer propagation
- serialization coverage for canonical `auto_review` output and legacy
`guardian_subagent` input
---------
Co-authored-by: saud-oai <saud@openai.com>
## Why
MCP startup failures from spawned subagents were rendered as global
notifications, so a child thread's failure could pollute the visible
parent transcript. Routing the notification to the child exposed two
related replay problems: session refresh could discard the buffered
event, and a newly created child `ChatWidget` did not know the expected
MCP server set, which could leave its startup spinner running after
every server had settled.
MCP startup diagnostics should remain visible in the thread that owns
the startup without affecting other transcripts. The protocol also needs
to support a future app-scoped MCP lifecycle where startup is not owned
by any thread.
## Reported Behavior
The [originating Slack
report](https://openai.slack.com/archives/C08JZTV654K/p1780604538859939)
called out that using subagents could turn MCP startup failures into a
wall of yellow CLI warnings because repeated failures were not
deduplicated. The intended behavior is for those diagnostics to remain
visible once in the thread that owns the startup, without polluting the
parent transcript.
## What Changed
- add nullable `threadId` ownership to `mcpServer/startupStatus/updated`
- populate it from the app-server conversation ID for the current
thread-scoped lifecycle and regenerate the protocol schema and
TypeScript artifacts
- treat a missing or null `threadId` as app-scoped without injecting it
into the active chat transcript
- route and buffer thread-owned MCP startup notifications by thread in
the TUI
- preserve buffered MCP startup events across child session refresh
- seed expected MCP servers before replaying a thread snapshot so
startup reaches its terminal state
- suppress an identical repeated failure warning for the same server
within one startup round
The owning thread still renders the detailed failure and final `MCP
startup incomplete (...)` summary.
## How to Test
1. Configure an optional MCP server named `smoke` that exits during
initialization.
2. Launch the TUI with multi-agent support enabled.
3. Confirm the main thread's own startup failure renders one detailed
`smoke` warning and one incomplete-startup summary.
4. Spawn exactly one subagent.
5. Confirm the parent transcript does not receive the subagent's MCP
startup failure.
6. Switch to the subagent thread and confirm it contains exactly one
detailed `smoke` failure and one incomplete-startup summary.
7. Confirm the subagent's MCP startup spinner disappears and the thread
remains usable.
8. Switch between the parent and subagent and confirm the warnings
neither move nor duplicate.
Targeted tests:
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server
thread_start_emits_mcp_server_status_updated_notifications`
- `just test -p codex-tui mcp_startup`
The parent/child behavior and spinner completion were also exercised
manually in tmux. `just argument-comment-lint` was attempted but blocked
by an unrelated local Bazel LLVM empty-glob failure; touched Rust
callsites were inspected manually.