Commit Graph

6553 Commits

Author SHA1 Message Date
aibrahim-oai
e1fd519589 Update models.json 2026-05-15 16:35:20 +00:00
Eric Traut
7fa0007ea8 tui: split remaining composer draft and footer state (#22656)
## Why

[#22581](https://github.com/openai/codex/pull/22581) started separating
the chat composer’s responsibilities, but `ChatComposer` still owned the
remaining editable draft state alongside footer/status presentation
state. This follow-up makes those ownership lines explicit so future
composer changes have a smaller blast radius and `BottomPane` does not
need to keep exposing scattered draft getters.

This is just a refactor. No functional or behavioral changes are
intended.

## What changed

- Move the remaining editable composer state into
`bottom_pane/chat_composer/draft_state.rs`.
- Move footer and status-row presentation state into
`bottom_pane/chat_composer/footer_state.rs`.
- Add an internal `ComposerDraftSnapshot` for restore flows, replacing
several ad hoc `BottomPane` pass-through reads.
- Rewire the related history-search and thread-input restore paths to
use the extracted state.

## Verification

- `RUST_MIN_STACK=8388608 cargo test -p codex-tui`
- `cargo insta pending-snapshots`
2026-05-15 09:12:52 -07:00
Michael Bolin
68ccfdc905 guardian: use permission profile for review sandbox (#22789)
## Why

`SandboxPolicy` is being pushed back toward legacy config loading and
compatibility boundaries. Guardian review sessions already want the
built-in read-only permission behavior; carrying that as an active
`PermissionProfile` makes the review sandbox follow the new permissions
path instead of configuring the child session through the legacy policy
API.

## What Changed

- Configure the guardian review session with
`PermissionProfile::read_only()`.
- Send the read-only profile through the guardian child `Op::UserTurn`.
- Keep the legacy `sandbox_policy` field populated with
`SandboxPolicy::new_read_only_policy()` declared next to the profile so
the two remain visibly in sync until the compatibility field goes away.

## How To Review

Start in `codex-rs/core/src/guardian/review_session.rs`. The important
check is that both the guardian config and the child turn now use the
read-only permission profile, while the remaining
`SandboxPolicy::ReadOnly` assignment is only the compatibility field
required by the current turn protocol.

## Verification

- `cargo test -p codex-core
guardian_review_session_config_clears_parent_developer_instructions`





---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22789).
* #22795
* #22792
* #22791
* #22790
* __->__ #22789
2026-05-15 08:59:31 -07:00
jif-oai
cccde930ce Move memory prompt injection to app-server extension (#22841)
## Why

Memory prompt injection should be owned by the extension path that
app-server composes at runtime, not by an inlined special case inside
`codex-core`. This keeps `codex-core` focused on session orchestration
while allowing the memories extension to own its app-server prompt
behavior.

## What Changed

- Registers `codex-memories-extension` in the app-server extension
registry.
- Moves the memory developer-instruction injection out of
`core/src/session/mod.rs` and into the memories extension prompt
contributor.
- Adds config-change handling so the extension keeps its per-thread
memory settings in sync after startup.
- Leaves memories read/retrieval tools unregistered for now so this PR
only changes prompt injection.
- Removes the stale `cargo-shear` ignore now that app-server depends on
the extension crate.

## Validation

Not run locally; validation is left to CI.
2026-05-15 16:19:34 +02:00
jif-oai
5d30764fe9 Run compact hooks for remote compaction v2 (#22828)
## Why

Remote compaction v2 is the `/responses` implementation of
session-history compaction, but it still needs to preserve the
observable contract of the legacy `/responses/compact` path. In
particular, users and integrations that rely on `PreCompact` and
`PostCompact` hooks should not see different behavior when
`remote_compaction_v2` is enabled.

## What Changed

- Runs `PreCompact` before issuing the remote compaction v2 request,
including `Interrupted` analytics when a pre-hook stops execution.
- Runs `PostCompact` after a successful v2 compaction and aborts the
turn if the post-hook stops execution.
- Adds `compact_remote_parity` coverage that compares legacy and v2
compaction across manual transcript shapes, automatic pre-turn
compaction, automatic mid-turn compaction, hook payloads, replacement
history, follow-up request payloads, and API-key `service_tier=fast`
behavior.
- Registers the new parity suite under `core/tests/suite`.

Relevant code:

-
[`compact_remote_v2.rs`](af63745cb5/codex-rs/core/src/compact_remote_v2.rs)
-
[`compact_remote_parity.rs`](af63745cb5/codex-rs/core/tests/suite/compact_remote_parity.rs)

## Verification

- Added `core/tests/suite/compact_remote_parity.rs` to assert parity
between legacy remote compaction and remote compaction v2 for the
affected request, hook, rollout-history, and follow-up paths.
- Existing `compact_remote_v2` unit coverage still exercises v2
replacement-history retention and compaction-output collection.
2026-05-15 15:26:21 +02:00
jif-oai
c03cea4ca2 Remove zombie tools spec module (#22820)
## Summary

- move tool_user_shell_type out of the old tools::spec module and call
it from tools directly
- attach the remaining spec planning model tests under spec_plan
- delete core/src/tools/spec.rs

## Tests

- just fmt
- cargo test -p codex-core tools::spec_plan

Note: a broader cargo test -p codex-core run on the earlier PR-head
worktree still hit the pre-existing stack overflow in
agent::control::tests::spawn_agent_fork_last_n_turns_keeps_only_recent_turns.
2026-05-15 13:44:58 +02:00
jif-oai
6f1a01fbdd Simplify tool executor and registry plumbing (#22636)
## Why

The tool runtime path still had a typed output associated type on
`ToolExecutor`, plus a core-only `RegisteredTool` adapter and
extension-only executor aliases. That made every new shared tool runtime
carry extra adapter plumbing before it could participate in core
dispatch, extension tools, hook payloads, telemetry, and model-visible
spec generation.

This PR moves output erasure to the shared executor boundary so core and
extension tools can use the same execution contract directly.

## What Changed

- Changed `codex_tools::ToolExecutor` to return `Box<dyn ToolOutput>`
instead of an associated `Output` type.
- Removed the extension-specific `ExtensionToolExecutor` /
`ExtensionToolOutput` aliases and exposed `ToolExecutor<ToolCall>` plus
`ToolOutput` through `codex-extension-api`.
- Reworked core tool registration around `CoreToolRuntime` and
`ToolRegistry::from_tools`, removing the extra `RegisteredTool` /
`ToolRegistryBuilder` layer.
- Consolidated model-visible spec planning and registry construction in
`core/src/tools/spec_plan.rs`, including deferred tool search and
code-mode-only filtering.
- Added `ToolOutput` helpers for post-tool-use hook ids and inputs so
MCP, unified exec, extension, and other boxed outputs preserve the same
hook payload behavior.
- Updated core handlers, memories tools, and the related
registry/spec/router tests to use the simplified contract.

## Test Coverage

- Updated coverage for tool spec planning, registry lookup, deferred
tool search registration, extension tool routing, post-tool-use hook
payloads, dispatch tracing, guardian output extraction, and memories
extension tool execution.
2026-05-15 11:47:54 +02:00
jif-oai
0322ac3df8 [codex] Use compaction_trigger item for remote compaction v2 (#22809)
## Why

Remote compaction v2 was still using `context_compaction` as both the
request trigger and the compacted output shape. The Responses API now
has the landed contract for this flow: Codex sends a dedicated `{
"type": "compaction_trigger" }` input item, and the backend returns the
standard `compaction` output item with encrypted content.

This aligns the v2 path with that wire contract while preserving the
existing local compacted-history post-processing behavior.

## What changed

- Add `ResponseItem::CompactionTrigger` and regenerate the app-server
protocol schema fixtures.
- Send `compaction_trigger` from `remote_compaction_v2` instead of a
payload-less `context_compaction`.
- Collect exactly one backend `compaction` output item, then reuse the
existing compacted-history rebuilding path.
- Treat the trigger item as a transient request marker rather than model
output or persisted rollout/memory content.

## Verification

- `cargo test -p codex-protocol compaction_trigger`
- `cargo test -p codex-core remote_compact_v2`
- `cargo test -p codex-core compact_remote_v2`
- `cargo test -p codex-core
responses_websocket_sends_response_processed_after_remote_compaction_v2`
- `just write-app-server-schema`
- `cargo test -p codex-app-server-protocol schema_fixtures`
2026-05-15 11:40:35 +02:00
jif-oai
a5e5faf216 Reject legacy [profiles] when using profile-v2 (#22647)
## Why

`profile-v2` layers the selected profile file on top of the base user
`config.toml`, but the legacy `[profiles]` table also stores named
profile overrides in that same base file. Allowing both paths during one
load makes it too easy to get a mixed profile where stale legacy
settings still influence a profile-v2 run.

## What Changed

- Detect a legacy `[profiles]` table in the base user config whenever
`--profile-v2` selects a profile file.
- Fail config loading with an `InvalidData` error that tells the user to
move those settings into the selected profile-v2 file or remove
`[profiles]`.
- Add a loader regression covering `--profile-v2` with legacy
`[profiles]` in `config.toml`.

## Testing

- `cargo test -p codex-config
profile_v2_rejects_legacy_profiles_in_base_user_config`
2026-05-15 11:35:42 +02:00
Shijie Rao
302149d979 Fix signed macOS release promotion follow-up jobs (#22788)
## Why

The `release_mode=promote_signed` path intentionally skips the build
jobs after signed macOS artifacts are staged, then runs the `release`
job from the signed handoff. In the `rust-v0.131.0-alpha.19` promotion
run, `release` succeeded but the npm, PyPI, and `latest-alpha-cli`
follow-up jobs were skipped because their custom job `if:` expressions
let GitHub Actions apply the implicit `success()` status check before
reading `needs.release.outputs.*`.

The unsigned build handoff does not need DotSlash manifests. Publishing
unsigned DotSlash manifests creates release assets that can conflict
with the later signed promotion, especially shared outputs such as
`bwrap`, `codex-command-runner`, and `codex-windows-sandbox-setup`.

## What Changed

- Stop publishing DotSlash manifests when `SIGN_MACOS == 'false'`.
- Delete `.github/dotslash-unsigned-config.json`.
- Gate post-release jobs with the `!cancelled()` status function plus an
explicit `needs.release.result == 'success'` check before consulting
release outputs.
- Keep the existing publish eligibility rules for npm, PyPI, WinGet, and
`latest-alpha-cli`.

## Verification

- `rg -n "dotslash-unsigned-config|SIGN_MACOS ==
'false'.*dotslash|unsigned-config" .github/workflows/rust-release.yml
.github || true`
- `git diff --check -- .github/workflows/rust-release.yml
.github/dotslash-unsigned-config.json`
2026-05-15 00:43:23 -07:00
Michael Bolin
8adb6032cc tui/exec: show effective workspace roots in summaries (#22612)
## Why

This PR builds on [#22611](https://github.com/openai/codex/pull/22611).

After `runtimeWorkspaceRoots` moved onto thread state, the user-facing
summaries were still inconsistent about which roots they showed. In
particular, `/status` and the exec startup summary could under-report
extra workspace roots from `--add-dir` or from profile-defined
`workspace_roots`, which made the new model look incorrect even when the
permissions themselves were right.

## What Changed

- switched the TUI status surfaces to summarize against
`Config::effective_workspace_roots()`
- updated the exec human-output summary to render from the effective
permission profile instead of the raw constrained profile
- added focused regressions for both the TUI and exec code paths so
extra workspace roots stay visible in user-facing summaries

## Verification

Targeted coverage for this follow-up lives in:
- `codex-rs/tui/src/status/tests.rs`
- `codex-rs/exec/src/event_processor_with_human_output_tests.rs`

The added regressions verify that:
- status output includes profile-defined workspace roots in the
effective permissions summary
- exec startup output includes runtime workspace roots instead of
collapsing back to `cwd` only
2026-05-14 23:10:45 -07:00
Michael Bolin
8a5306ff88 app-server: use permission ids and runtime workspace roots (#22611)
## Why

This PR builds on [#22610](https://github.com/openai/codex/pull/22610)
and is the app-server side of the migration from mutable per-turn
`SandboxPolicy` replacement toward selecting immutable permission
profiles by id plus mutable runtime workspace roots.

Once permission profiles can carry their own immutable
`workspace_roots`, app-server no longer needs to mutate the selected
`PermissionProfile` just to represent thread-specific filesystem
context. The mutable part now lives on the thread as explicit
`runtimeWorkspaceRoots`, while `:workspace_roots` remains symbolic until
the sandbox is realized for a turn.

## What Changed

- Replaced the v2 permission-selection wrapper surface with plain
profile ids for `thread/start`, `thread/resume`, `thread/fork`, and
`turn/start`.
- Removed the API surface for profile modifications
(`PermissionProfileSelectionParams`,
`PermissionProfileModificationParams`,
`ActivePermissionProfileModification`).
- Added experimental `runtimeWorkspaceRoots` fields to the thread
lifecycle and turn-start APIs.
- Threaded runtime workspace roots through core session/thread
snapshots, turn overrides, app-server request handling, and command
execution permission resolution.
- Kept session permission state symbolic so later runtime root updates
and cwd-only implicit-root retargeting rebind `:workspace_roots`
correctly.
- Updated the embedded clients just enough to send and restore the new
thread state.
- Refreshed the generated schema/TypeScript artifacts and the app-server
README to match the new contract.

## Verification

Targeted coverage for this layer lives in:

- `codex-rs/app-server-protocol/src/protocol/v2/tests.rs`
- `codex-rs/app-server/tests/suite/v2/thread_start.rs`
- `codex-rs/app-server/tests/suite/v2/thread_resume.rs`
- `codex-rs/app-server/tests/suite/v2/turn_start.rs`
- `codex-rs/core/src/session/tests.rs`

The key regression checks exercise that:

- `runtimeWorkspaceRoots` resolve against the effective cwd on thread
start.
- Profile-declared workspace roots are excluded from the runtime
workspace roots returned by app-server.
- A turn-level runtime workspace-root update persists onto the thread
and is returned by `thread/resume`.
- A named permission profile selected on one turn remains symbolic so a
later runtime-root-only turn update changes the actual sandbox writes.
- A cwd-only turn update retargets the implicit runtime cwd root while
preserving additional runtime roots.
- The protocol fixtures and generated client artifacts stay in sync with
the string-based permission selection contract.











---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22611).
* #22612
* __->__ #22611
2026-05-14 23:00:05 -07:00
Eric Traut
e6a7368810 TUI: split history cells into focused modules (#22704)
## Why

`codex-rs/tui/src/history_cell.rs` had become the dumping ground for
transcript rendering: the shared trait, common helpers, and the concrete
cells for messages, plans, MCP/search, notices, patches, approvals,
session chrome, and separators all lived together. That made small
transcript changes require reopening a very large file and made
ownership less obvious.

## What changed

- Replaced the monolithic `history_cell.rs` with a `history_cell/`
module tree organized by concern.
- Kept the existing `crate::history_cell::*` surface stable through
re-exports in `history_cell/mod.rs`.
- Moved the existing render coverage into `history_cell/tests.rs`.

## Reviewer notes

- This PR is intentionally mechanical in mature — existing code and
tests moving into files that match their concern.
- The snapshot files under `codex-rs/tui/src/history_cell/snapshots/`
moved with the extracted test module. `insta` resolves these unnamed
snapshots relative to the source file that declares them, so this is
path churn only; snapshot contents were not updated.
- The small non-mechanical seam edits are limited to split fallout:
sibling-module visibility for shared cell containers, moving
approval-specific exec-snippet helpers beside approvals, fixing the
separator module path, and keeping a couple of existing test helpers
reachable after extraction.
2026-05-14 21:19:06 -07:00
Eric Traut
d1235a0a78 Prevent Esc from dismissing or rewinding /side (#22710)
Addresses #22599

## Why
`/side` currently lets `Esc` return to the parent thread. Multiple users
reported that this collides with queued-steer UI that also advertises
`Esc`, so a timing-sensitive keypress can dismiss an ephemeral side chat
instead of sending the queued prompt.

After removing that dismissal shortcut, the same `Esc` path could fall
through to main-thread backtrack/edit-previous handling, which is not
valid for ephemeral side conversations. This keeps `/side` out of both
global `Esc` behaviors.

## What changed
- Remove `Esc` from the `/side` return shortcut matcher while keeping
the existing `Ctrl+C` and `Ctrl+D` behavior.
- Update side-conversation hints and blocked-command copy to advertise
`Ctrl+C` as the return shortcut.
- Rename the reserved `Esc` keymap label to describe backtracking only.
- Block backtrack/edit-previous handling while a side conversation is
active and report `Editing previous prompts is unavailable in side
conversations.` when that path would have fired.
- Keep composer-owned `Esc` behavior, such as Vim insert-mode escape,
routed locally.
- Refresh focused shortcut assertions and TUI snapshots for the updated
footer and new side-conversation error message.

## Verification
Manually tested `/side` use cases and `Esc`, `Ctrl+C`, `Ctrl+D`.
2026-05-14 20:51:08 -07:00
guinness-oai
4f2918dd7f [codex] Add opaque desktop config namespace (#22584)
## Summary
- reserve an explicit opaque `desktop` namespace in `ConfigToml`
- expose `desktop` directly in the app-server v2 `config/read` response
- keep `config/value/write` and `config/batchWrite` as the only mutation
seam for paths like `desktop.someKey`
- regenerate the config/app-server schema outputs and document the new
contract

## Why
The desktop settings work wants one durable, user-editable home for
app-owned preferences in `~/.codex/config.toml`, without forcing Rust to
model every individual desktop setting key.

This PR is only the enabling Rust/app-server layer. It gives the
Electron app a first-class config namespace it can read and write
through the existing config APIs, while leaving the actual desktop
migration to the app PR.

## Behavior and design notes
- **Opaque but explicit:** `desktop` is first-class at the typed config
root, while its children remain app-owned and open-ended.
- **Strict validation still works:** arbitrary nested `desktop.*` keys
are accepted instead of being rejected as unknown config.
- **Existing config APIs stay the seam:** `config/read` returns the bag,
and dotted writes such as `desktop.someKey` continue to flow through
`config/value/write` / `config/batchWrite` rather than a bespoke RPC.
- **No new consumer behavior:** Core/TUI do not start depending on
desktop preferences. This only preserves and exposes the namespace for
callers that intentionally use it.
- **Same persistence machinery:** hand-edited `config.toml` keeps using
the existing TOML edit/write path; this PR does not introduce a second
serializer or side channel.
- **TOML-friendly values:** the namespace is intended for ordinary
JSON-shaped setting values that map cleanly into TOML: strings, numbers,
booleans, arrays, and nested object/table values. This PR does not add
special handling for TOML-only edge cases such as datetimes.

## Layering semantics
Reads keep using the ordinary effective config pipeline, so `desktop`
participates in the same layered `config/read` behavior as the rest of
`ConfigToml`. Writes still target user config through the existing
config service.

## Why this is the shape
The alternative would be teaching Rust about each desktop setting as it
is added. That would make ordinary app preferences into a cross-repo
change, which is exactly the coupling we want to avoid.

This keeps the contract small:
1. Rust owns one opaque `desktop` namespace in `config.toml`.
2. The desktop app owns the schema and meaning of individual keys inside
it.
3. The existing config APIs remain the transport and mutation surface.

That is the piece the desktop settings PR needs in order to move forward
cleanly.

## Verification
- `cargo test -p codex-config strict_config_accepts_opaque_desktop_keys`
- `cargo test -p codex-core
desktop_toml_round_trips_opaque_nested_values`
- `cargo test -p codex-core config_schema_matches_fixture`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server --test all desktop_settings`
2026-05-15 02:34:21 +00:00
Eric Traut
3a23e87e20 tui: recover local state db startup failures (#22734)
## Why

#22580 made app-server startup fail when the local SQLite state database
cannot be initialized. Embedded/local TUI startup still continued on the
permissive path, which left the CLI inconsistent and could hide a real
startup problem behind unrelated UI. This brings local TUI startup onto
the same fail-closed behavior while keeping recovery humane for the two
failure modes we are seeing in practice: damaged database files and
startup stalls caused by another process holding the database write
lock.

## What changed

- Embedded TUI startup now uses `state_db::try_init(...)` and returns a
typed `LocalStateDbStartupError` that preserves the affected database
path plus the underlying failure detail.
- CLI startup handles that failure before entering the interactive TUI:
- lock-contention failures tell users to quit other Codex processes and
try again
- failures consistent with a broken local database offer a safe repair
that backs up Codex-owned SQLite files, rebuilds local database files,
and retries startup once
- declined or unsuccessful repairs print concise guidance plus technical
details
- Shared startup error plumbing lives in `tui/src/startup_error.rs`,
while CLI recovery policy and focused recovery tests live in
`cli/src/state_db_recovery.rs`.

## Verification

- `cargo test -p codex-tui
embedded_state_db_failure_is_typed_for_cli_recovery`
- `cargo test -p codex-cli state_db_recovery`
- Manually held an exclusive SQLite lock on `state_5.sqlite` and
confirmed the CLI shows lock-specific guidance without offering repair.
- Manually exercised the repair path with a deliberately invalid
`sqlite_home` and confirmed it backs up the blocking path and resumes
startup.
2026-05-14 18:51:36 -07:00
Michael Bolin
3c6d727810 permissions: resolve profile identity with constraints (#22683)
## Why

This PR is the invariant-cleanup layer that follows the workspace-roots
base merged in [#22610](https://github.com/openai/codex/pull/22610).

#22610 adds `[permissions.<id>.workspace_roots]` and keeps runtime
workspace roots separate from the raw permission profile, but its
in-memory representation is intentionally transitional: `Permissions`
still carries the selected profile identity next to a constrained
`PermissionProfile`. That makes APIs such as
`set_constrained_permission_profile_with_active_profile()` fragile
because the id and value only mean the right thing when every caller
keeps them in sync.

This PR introduces a single resolved profile state so profile identity,
`extends`, the profile value, and profile-declared workspace roots
travel together. The next PR,
[#22611](https://github.com/openai/codex/pull/22611), builds on this by
changing the app-server turn API to select permission profiles by id
plus runtime workspace roots.

## Stack Context

- #22610, now merged: adds profile-declared `workspace_roots`, runtime
workspace roots, and `:workspace_roots` materialization.
- This PR: replaces the parallel active-profile/profile-value fields
with `PermissionProfileState`.
- #22611: switches app-server turn updates toward profile ids plus
runtime workspace roots.
- #22612: updates TUI/exec summaries to show the effective workspace
roots.

Keeping this separate from #22611 is deliberate: reviewers can validate
the internal state invariant before reviewing the app-server protocol
migration.

## What Changed

- Added `ResolvedPermissionProfile::{Legacy, BuiltIn, Named}` and
`PermissionProfileState`.
- Typed built-in profile ids with `BuiltInPermissionProfileId`.
- Moved selected profile identity and profile-declared workspace roots
into the resolved state.
- Replaced `Permissions` parallel profile fields with one
`permission_profile_state`.
- Removed `set_constrained_permission_profile_with_active_profile()`
from session sync paths.
- Kept trusted session replay/`SessionConfigured` compatibility through
explicit session snapshot helpers.
- Updated session configuration, MCP initialization, app-server, exec,
TUI, and guardian call sites to consume `&PermissionProfile` directly.

## Review Guide

Start with `codex-rs/core/src/config/resolved_permission_profile.rs`; it
is the new invariant boundary. Then review
`codex-rs/core/src/config/mod.rs` to see how config loading records
active profile identity and profile workspace roots. The remaining
call-site changes are mostly mechanical fallout from
`Permissions::permission_profile()` returning `&PermissionProfile`
instead of `&Constrained<PermissionProfile>`.

## Verification

The existing config/session coverage now constructs and asserts through
`PermissionProfileState`. The workspace-root config test also asserts
that profile-declared roots are preserved in the resolved state, which
is the behavior #22611 relies on when runtime roots become mutable
through the app-server API.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22683).
* #22612
* #22611
* __->__ #22683
2026-05-14 18:47:44 -07:00
Dylan Hurd
06bb508547 Stabilize compact rollback follow-up test (#22303)
## Summary
- add the missing response.created event to the mocked empty follow-up
response in the compact rollback test
- keep the fix scoped to the flaky mocked stream shape, without
increasing timeouts

## Recent flakes on main
- `snapshot_rollback_followup_turn_trims_context_updates` failed in
`rust-ci-full` on `main` in the Ubuntu remote test job on 2026-05-14:
https://github.com/openai/codex/actions/runs/25891434395/job/76095284830
- The same `compact_resume_fork` suite also failed recently on `main`
with `snapshot_rollback_past_compaction_replays_append_only_history`,
which has the same mocked Responses stream shape sensitivity this PR is
tightening:
https://github.com/openai/codex/actions/runs/25892437363/job/76098329098

## Verification
- env -u CODEX_SANDBOX_NETWORK_DISABLED cargo test -p codex-core --test
all snapshot_rollback_followup_turn_trims_context_updates -- --nocapture
- repeated the same focused test 3 consecutive times locally
- UV_CACHE_DIR=/private/tmp/uv-cache-codex-fmt just fmt
2026-05-14 18:43:18 -07:00
Michael Bolin
db51df0f44 ci: support signed macOS release promotion (#22737)
## Why

`rust-release.yml` can create unsigned macOS artifacts for external
signing, but there was no signed resume path after those artifacts
returned from a secure enclave. Release operators need a way to reuse
the first run artifacts, ingest signed macOS binaries and DMGs, and
continue the normal signed release path without rebuilding every
platform or treating handoff assets as final release assets.

## How this is meant to be used

First, start the release as an unsigned macOS build against the release
tag:

```shell
gh workflow run rust-release.yml \
  --repo openai/codex \
  --ref rust-vX.Y.Z \
  -f release_mode=build_unsigned
```

That run builds the normal Linux/Windows artifacts and publishes
unsigned macOS handoff artifacts. The unsigned macOS binaries are then
copied to the secure enclave, signed and notarized there, packaged as a
signed handoff archive, and uploaded back to the GitHub Release for the
same tag.

The signed handoff asset should contain either target directories such
as `aarch64-apple-darwin/` and `x86_64-apple-darwin/`, or artifact
directories such as `aarch64-apple-darwin-app-server/`. The promote
workflow accepts either layout. The directories should contain the
signed binaries and, for primary macOS bundles, the signed and stapled
DMGs.

For example, after signing, upload the handoff asset to the release:

```shell
gh release upload rust-vX.Y.Z \
  signed-macos-rust-vX.Y.Z.tar.zst \
  --repo openai/codex \
  --clobber
```

Then start the promotion run. `unsigned_run_id` is the workflow run id
from the first `build_unsigned` run, and `signed_macos_asset` is the
exact Release asset name uploaded by the secure enclave:

```shell
gh workflow run rust-release.yml \
  --repo openai/codex \
  --ref rust-vX.Y.Z \
  -f release_mode=promote_signed \
  -f unsigned_run_id=1234567890 \
  -f signed_macos_asset=signed-macos-rust-vX.Y.Z.tar.zst \
  -f signed_macos_sha256=<sha256>
```

The `signed_macos_sha256` input is optional, but when provided the
promotion run verifies the handoff archive before unpacking it. The
promotion run also validates that `unsigned_run_id` points to a
successful manual `rust-release` run for the same tag and commit before
importing artifacts.

## What Changed

- Add explicit manual `release_mode` values for `build_unsigned` and
`promote_signed` while keeping `sign_macos` as a deprecated
compatibility input.
- Add promote inputs for `unsigned_run_id`, `signed_macos_asset`, and
optional `signed_macos_sha256`.
- Add a `stage-signed-macos` job that downloads the signed handoff asset
from the GitHub Release, verifies signed binaries and stapled DMGs,
repacks normal macOS release artifacts, and builds macOS Python runtime
wheels.
- Teach the release job to download Part 1 artifacts from the unsigned
run, discard unsigned macOS staging artifacts, re-upload promoted Linux
and Windows artifacts for npm staging, and then run the signed release
tail.
- Validate that `unsigned_run_id` points to a successful manual
`rust-release` run for the same tag and commit before importing
artifacts.
- Limit unsigned macOS artifact upload to the unsigned build path so
normal signed releases do not publish unsigned handoff binaries.
- Clean up unsigned and signed handoff release assets after successful
promotion.

## Verification

- Parsed `.github/workflows/rust-release.yml` with Ruby YAML loading.

No developers.openai.com documentation update is needed.
2026-05-14 18:36:20 -07:00
mchen-oai
10cf1f79dd Add user_input_requested_during_turn to MCP turn metadata (#22237)
## Why
- Similar change as https://github.com/openai/codex/pull/21219
- Without change: MCP tool calls receive
`_meta["x-codex-turn-metadata"]` with various key values.
- Issue: MCP servers currently do not know if user input was requested
during the turn (Ex: Model decides to prompt the user for approval
mid-turn before making a possibly risky tool call). MCP servers may want
to know this when tracking latency metrics because these instances are
inflated.

## What Changed
- With change: MCP turn metadata now includes
`user_input_requested_during_turn` when a model-visible
`request_user_input` call happened earlier in the turn, propagated in
`_meta["x-codex-turn-metadata"]`.
- `mark_turn_user_input_requested()` is called when user input is
requested through either MCP elicitation (`mcp.rs`) or the
`request_user_input` tool (`mod.rs`).
- MCP tool call `_meta` is now built immediately before execution
(`mcp_tool_call.rs`) so user input requested earlier in the same turn,
including within the same tool call via elicitation, is reflected in the
metadata.
- Normal `/responses` turn metadata headers are unchanged.

## Verification
- `codex-rs/core/src/session/mcp_tests.rs`
- `codex-rs/core/src/tools/handlers/request_user_input_tests.rs`
- `codex-rs/core/src/turn_metadata_tests.rs`
- `codex-rs/core/tests/suite/search_tool.rs`
2026-05-15 01:26:50 +00:00
Michael Bolin
c25d905f61 permissions: support workspace roots in profiles (#22610)
## Why

This is the configuration/model half of the alternative permissions
migration we discussed as a comparison point for
[#22401](https://github.com/openai/codex/pull/22401) and
[#22402](https://github.com/openai/codex/pull/22402).

The old `workspace-write` model mixes three concerns that we want to
keep separate:
- reusable profile rules that should stay immutable once selected
- user/runtime workspace roots from `cwd`, `--add-dir`, and legacy
workspace-write config
- internal Codex writable roots such as memories, which should not be
shown as user workspace roots

This PR gives permission profiles first-class `workspace_roots` so users
can opt multiple repositories into the same `:workspace_roots` rules
without using broad absolute-path write grants. It also starts
separating the raw selected profile from the effective runtime profile
by making `Permissions` expose explicit accessors instead of public
mutable fields.

A representative `config.toml` looks like this:

```toml
default_permissions = "dev"

[permissions.dev.workspace_roots]
"~/code/openai" = true
"~/code/developers-website" = true

[permissions.dev.filesystem.":workspace_roots"]
"." = "write"
".codex" = "read"
".git" = "read"
".vscode" = "read"
```

If Codex starts in `~/code/codex` with that profile selected, the
effective workspace-root set becomes:
- `~/code/codex` from the runtime `cwd`
- `~/code/openai` from the profile
- `~/code/developers-website` from the profile

The `:workspace_roots` rules are materialized across each root, so
`.git`, `.codex`, and `.vscode` stay scoped the same way everywhere.
Runtime additions such as `--add-dir` can still layer on later stack
entries without mutating the selected profile.

## Stack Shape

This PR intentionally stops before the profile-identity cleanup in
[#22683](https://github.com/openai/codex/pull/22683) so the base review
stays focused on config loading, workspace-root materialization, and
compatibility with legacy `workspace-write`.

The representation in this PR is therefore transitional: `Permissions`
carries enough state to distinguish the raw constrained profile from the
effective runtime profile, and there are still call sites that must keep
the active profile identity and constrained profile value in sync. The
follow-up PR replaces that with a single resolved profile state
(`ResolvedPermissionProfile` / `PermissionProfileState`) that keeps the
profile id, immutable `PermissionProfile`, and profile-declared
workspace roots together. That follow-up removes APIs such as
`set_constrained_permission_profile_with_active_profile()` where
separate arguments could drift out of sync.

Downstream PRs then build on this base to switch app-server turn updates
to profile ids plus runtime workspace roots and to finish the
user-visible summary behavior. Reviewers should judge this PR as the
workspace-roots foundation, not as the final in-memory shape of selected
permission profiles.

## Review Guide

Suggested review order:

1. Start with `codex-rs/core/src/config/mod.rs`.
This is the main shape change in the base slice. `Permissions` now
stores a private raw `Constrained<PermissionProfile>` plus runtime
`workspace_roots`. Callers use `permission_profile()` when they need the
raw constrained value and `effective_permission_profile()` when they
need a materialized runtime profile. As noted above,
[#22683](https://github.com/openai/codex/pull/22683) replaces this
transitional shape with a resolved profile state that keeps identity and
profile data together.

2. Review `codex-rs/config/src/permissions_toml.rs` and
`codex-rs/core/src/config/permissions.rs`.
These add `[permissions.<id>.workspace_roots]`, resolve enabled entries
relative to the policy cwd, and keep `:workspace_roots` deny-read glob
patterns symbolic until the actual roots are known.

3. Review `codex-rs/protocol/src/permissions.rs` and
`codex-rs/protocol/src/models.rs`.
These add the policy/profile materialization helpers that expand exact
`:workspace_roots` entries and scoped deny-read globs over every
workspace root. This is also where `ActivePermissionProfileModification`
is removed from the core model.

4. Review the legacy bridge in
`Config::load_from_base_config_with_overrides` and
`Config::set_legacy_sandbox_policy`.
This is where legacy `workspace-write` roots become runtime workspace
roots, while Codex internal writable roots stay internal and do not
appear as user-facing workspace roots.

5. Then skim downstream call sites.
The interesting pattern is raw-vs-effective access: state/proxy/bwrap
paths keep the raw constrained profile, while execution, summaries, and
user-visible status use the effective profile and workspace-root list.

## What Changed

- added `[permissions.<id>.workspace_roots]` to the config model and
schema
- added runtime `workspace_roots` state to `Config`/`Permissions` and
`ConfigOverrides`
- made `Permissions` profile fields private and replaced direct mutation
with accessors/setters
- added `PermissionProfile` and `FileSystemSandboxPolicy` helpers for
materializing `:workspace_roots` exact paths and deny-read globs across
all roots
- moved legacy additional writable roots into runtime workspace-root
state instead of active profile modifications
- removed `ActivePermissionProfileModification` and its app-server
protocol/schema export
- updated sandbox/status summary paths so internal writable roots are
not reported as user workspace roots

## Verification Strategy

The targeted tests cover the behavior at the layers where regressions
are most likely:
- `codex-rs/core/src/config/config_tests.rs` verifies config loading,
legacy workspace-root seeding, effective profile materialization, and
memory-root handling.
- `codex-rs/core/src/config/permissions_tests.rs` verifies profile
`workspace_roots` parsing and `:workspace_roots` scoped/glob
compilation.
- `codex-rs/protocol/src/permissions.rs` unit tests verify exact and
glob materialization over multiple workspace roots.
- `codex-rs/tui/src/status/tests.rs` and
`codex-rs/utils/sandbox-summary/src/sandbox_summary.rs` verify the
user-facing summaries show effective workspace roots and hide internal
writes.

I also ran `cargo check --tests` locally after the latest stack refresh
to catch cross-crate API breakage from the private-field/accessor
changes.







---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22610).
* #22612
* #22611
* #22683
* __->__ #22610
2026-05-14 18:25:23 -07:00
Dylan Hurd
7dbe1c9498 [codex] Remove experimental instructions file config (#22724)
## Summary

Remove the deprecated `experimental_instructions_file` config setting
from the typed config surface and the remaining deprecation-notice
plumbing. `model_instructions_file` remains the supported setting and
its loading path is unchanged.

The setting was deprecated when it was renamed to
`model_instructions_file` on January 20, 2026 in
https://github.com/openai/codex/pull/9555.

## Changes

- Remove `experimental_instructions_file` from `ConfigToml` and
`ConfigProfile`.
- Delete the custom config-layer scan and session deprecation notice for
the removed setting.
- Stop clearing the removed field from generated session config locks.
- Remove the obsolete deprecation-notice test case while keeping
`model_instructions_file` coverage intact.

## Validation

- `just write-config-schema`
- `just fmt`
- `cargo test -p codex-config`
- `cargo test -p codex-core model_instructions_file`
- `just fix -p codex-core`
- `git diff --check`

Co-authored-by: Codex <noreply@openai.com>
2026-05-14 18:04:26 -07:00
Dylan Hurd
eeabaf74ea [codex] Group removed feature flags (#22730)
## Summary
- move removed feature enum variants under the existing Removed section
- keep active feature variants grouped away from no-op compatibility
flags

## Test plan
- just fmt
- cargo test -p codex-features

Co-authored-by: Codex <noreply@openai.com>
2026-05-15 00:53:13 +00:00
pakrym-oai
4bff020a96 Remove SSE fixture loaders (#22684)
## Why

The Responses API test support already has structured SSE event
builders. Keeping separate JSON fixture loaders made small mock streams
harder to read and left an on-disk fixture for a single event.

## What changed

- Removed `load_sse_fixture` and `load_sse_fixture_with_id_from_str`
from `core_test_support`.
- Deleted the one `tests/fixtures/incomplete_sse.json` Responses API
fixture.
- Replaced the remaining call sites with `responses::sse(...)` and
existing event helpers.

## Validation

- `cargo test -p codex-core --test all
stream_no_completed::retries_on_early_close`
- `cargo test -p codex-core --test all
history_dedupes_streamed_and_final_messages_across_turns`
- `cargo test -p codex-core --test all review::`
2026-05-15 00:40:32 +00:00
canvrno-oai
66af217865 Fix /review mode MCP startup render issue (#21624)
This change fixes the case where the UI can sit on _"Starting MCP
servers"_ even though the review work is already running or has already
completed.

- MCP startup status header is visible when a `/review` turn starts with
enabled MCP server startups
- Restore the underlying _Working..._ status after MCP startup completes
or fails
- Add regression coverage for overlapping startup/turn flows and status
restoration

_De-scoped from a broader thread-scoped MCP status change that would
have made it easier to route MCP startup statuses to the appropriate
thread (parent vs. review). These changes address the UI regression
without requiring more significant changes across app-server & core._

Fixes #18792.
2026-05-14 17:25:32 -07:00
Eric Traut
3dc278b68e Trim TUI legacy core helper usage (#22695)
## Why

The TUI still had a few low-risk dependencies flowing through the
transitional `legacy_core` namespace after the app-server migration.
These helpers either already have clearer non-core owners or are
presentation logic that does not belong in `codex-core`, so moving them
out reduces the compatibility surface without changing product behavior.

## What changed

This is a low-risk change, almost completely mechanical in nature.

- Route TUI Codex-home lookup through `codex-utils-home-dir`, use
`Config::log_dir` directly, and call
`codex-sandboxing::system_bwrap_warning` without going through
`legacy_core`.
- Move shared `codex resume` hint formatting from `codex-core` into
`codex-utils-cli`.
- Update CLI and TUI call sites to use the shared CLI utility, and keep
the resume-command behavior covered by tests in its new home.

## Verification

- `cargo test -p codex-utils-cli`
- `cargo test -p codex-utils-cli resume_command`
2026-05-14 16:54:59 -07:00
Dylan Hurd
85915a2a21 chore(config) rm windows_wsl_setup_acknowledged (#22717)
## Summary
Remove dead code from a notice that no longer exists.

## Testing
- [x] Unit tests pass.
2026-05-14 23:25:15 +00:00
Dylan Hurd
51b0e94105 chore(features) rm Feature::ApplyPatchFreeform (#22711)
## Summary
Removes the feature since this is effectively on by default in all cases
where we should use it, or can be configured via models.json.

## Testing
- [x] unit tests pass
2026-05-14 16:15:56 -07:00
starr-openai
7c11c14efc Fix Windows sandbox clippy clones (#22687)
## Summary
- remove two redundant `PathBuf` clones in Windows sandbox setup tests
- fix current `rust-ci-full` Windows clippy failures on `main`

## Validation
- `just fmt`
- attempted on `dev`: `cargo clippy --target x86_64-pc-windows-msvc
--tests --profile dev --timings -- -D warnings`
- blocked by missing MSVC cross toolchain on the Linux devbox (`lib.exe`
/ MSVC C toolchain unavailable)
- live failure evidence: main `rust-ci-full` runs 25880209898 and
25879137967 failed on `windows-sandbox-rs/src/bin/setup_main/win.rs`
with `clippy::redundant_clone` at the two edited callsites
2026-05-14 15:54:18 -07:00
xli-oai
8c7a176b55 Unqueue plugin list and read requests (#22703)
## Summary
- remove the app-server `plugin-read` serialization queue from
`plugin/list` and `plugin/read`
- allow plugin read/list requests to start immediately instead of
waiting behind other plugin read/list requests

## Test plan
- `just fmt`
- `cargo test -p codex-app-server-protocol`
2026-05-14 15:07:20 -07:00
sayan-oai
d346957288 make rust-release-prepare use env secret (#22702)
made a `rust-release-prepare` environment with the necessary API key as
an environment secret. use this in the workflow rather than the action
secret.

once this merges and i confirm it works as intended, ill rm the action
secret.
2026-05-14 21:45:53 +00:00
rreichel3-oai
02a7205250 [codex] Support multiple forced ChatGPT workspaces (#18161)
## Summary

This change lets `forced_chatgpt_workspace_id` accept multiple workspace
IDs instead of a single value.

It keeps the existing config key name, adds backward-compatible parsing
for a single string in `config.toml`, and normalizes the setting into an
allowed workspace list across login enforcement, app-server config
surfaces, and local ChatGPT auth helpers.

## Why

Workspace-restricted deployments may need to allow more than one ChatGPT
workspace without dropping the guardrail entirely.

## Server-side impact

Codex's local server and app-server protocol needed changes because they
previously assumed a single workspace ID. The local login flow now
matches the auth backend interface by sending the allowed workspace list
as a single comma-separated `allowed_workspace_id` query parameter.

## Validation

This was tested with:

- A single workspace config
- With multi-workspace configs
- With multiple workspaces in the config
- The user only being a part of a subset of them

All were successful.

Automated coverage:

- `cargo test -p codex-login`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-tui local_chatgpt_auth`
- `cargo test --locked -p codex-app-server
login_account_chatgpt_includes_forced_workspace_allowlist_query_param`
2026-05-14 17:11:36 -04:00
starr-openai
32b45a43e2 tests: isolate codex home for live cli (#22563)
## Why

Some core integration-test paths were creating Codex state under ambient
`~/.codex`. In environments where `HOME=/tmp`, that showed up as
`/tmp/.codex`, which is host-level shared state and makes these tests
environment/order sensitive.

The affected paths were:

- `core/tests/suite/live_cli.rs`: `run_live()` spawned the real CLI with
a temp cwd, but without an isolated home, so the child resolved Codex
home from ambient `HOME`.
- core / exec-server integration test binaries using
`configure_test_binary_dispatch(...)`: their startup ctor installs arg0
helper aliases like `apply_patch` and `codex-linux-sandbox`. Full
`arg0_dispatch()` also installs aliases from ambient Codex-home
resolution, so test-binary startup could create `CODEX_HOME/tmp/arg0`;
with `HOME=/tmp`, that became `/tmp/.codex/tmp/arg0/...`.

## What changed

- `live_cli` now gives the spawned CLI a temp `HOME` and temp
`CODEX_HOME`.
- arg0 alias setup now has an explicit-home form,
`prepend_path_entry_for_codex_aliases_in(...)`, so test helpers can
place alias state under a temp directory without relying on ambient
`CODEX_HOME`.
- helper re-entry behavior is preserved with
`dispatch_arg0_if_needed()`, so aliases like `apply_patch` and
`codex-linux-sandbox` still dispatch correctly before test alias
installation.
- core test support keeps the temp Codex home alive for the lifetime of
the test binary, matching the alias lifetime.

## Verification

Verified on `dev2` with `HOME=/tmp` that the focused core test-binary
startup path no longer recreates `/tmp/.codex`.

Also checked the exact `live_cli` test path under `HOME=/tmp`; on `dev2`
it still hits the existing remote-only `cargo_bin("codex-rs")`
resolution failure before spawning the child, but `/tmp/.codex` remains
absent after the run.
2026-05-14 12:59:56 -07:00
starr-openai
255748638c Fix remote environment test fixtures (#22572)
## Why
The Docker remote-env coverage was failing before it reached the
behavior those tests are meant to exercise. The remote-aware test
fixture only registered the remote environment, so tests that
intentionally select both `local` and `remote` could not start a turn.
After that was fixed, two tests exposed stale fixtures: the approval
test was auto-approving under workspace-write, and the remote
`view_image` test was writing invalid PNG bytes.

## What Changed
- Added `EnvironmentManager::create_for_tests_with_local(...)` so tests
can keep the provider default while also selecting `local` explicitly.
- Updated `build_remote_aware()` to use that test-only manager when a
remote exec-server URL is present.
- Changed the remote apply-patch approval helper to use
`SandboxPolicy::new_read_only_policy()` so the test actually exercises
approval caching per environment.
- Replaced the hardcoded remote `view_image` PNG blob with the existing
`png_bytes(...)` helper so the test uses a valid image fixture.

## Validation
Ran these isolated Docker remote-env tests on the devbox with
`$remote-tests` setup:
-
`suite::remote_env::apply_patch_freeform_routes_to_selected_remote_environment`
-
`suite::remote_env::apply_patch_approvals_are_remembered_per_environment`
-
`suite::remote_env::apply_patch_intercepted_exec_command_routes_to_selected_remote_environment`
-
`suite::remote_env::exec_command_routes_to_selected_remote_environment`
- `suite::view_image::view_image_routes_to_selected_remote_environment`

All five pass.
2026-05-14 12:40:01 -07:00
Michael Bolin
e8969d940d test: isolate exec review policy config test (#22512)
## Why


`thread_start_params_include_review_policy_when_review_policy_is_manual_only`
builds a `Config` with a temporary `CODEX_HOME`, but
`ConfigBuilder::default()` can still load host-managed configuration. On
local macOS machines with enterprise-managed Codex config, that host
state can leak into the test and change the resulting config, even
though CI does not have the same managed config source.

This makes the test environment-dependent: it can pass in CI while
failing locally for developers who have managed configuration installed.

## What Changed

- Updated `codex-rs/exec/src/lib_tests.rs` so the test calls
`LoaderOverrides::without_managed_config_for_tests()` through
`ConfigBuilder::loader_overrides(...)`.
- Left the rest of the test setup intact, including the temporary
`CODEX_HOME`, temporary cwd, and explicit `approvals_reviewer` harness
override.

## Verification

```shell
cargo test -p codex-exec thread_start_params_include_review_policy_when_review_policy_is_manual_only
```
2026-05-14 12:14:20 -07:00
Matthew Zeng
d8ddeb6869 Support explicit MCP OAuth client IDs (#22575)
## Why
Some MCP OAuth providers require a pre-registered public client ID and
cannot rely on dynamic client registration. Codex already supports MCP
OAuth, but it had no way to supply that client ID from config into the
PKCE flow.

## What changed
- add `oauth.client_id` under `[mcp_servers.<server>]` config, including
config editing and schema generation
- thread the configured client ID through CLI, app-server, plugin login,
and MCP skill dependency OAuth entrypoints
- configure RMCP authorization with the explicit client when present,
while preserving the existing dynamic-registration path when it is
absent
- add focused coverage for config parsing/serialization and OAuth URL
generation

## Verification
- `cargo test -p codex-config -p codex-rmcp-client -p codex-mcp -p
codex-core-plugins`
- `cargo test -p codex-core blocking_replace_mcp_servers_round_trips
--lib`
- `cargo test -p codex-core
replace_mcp_servers_streamable_http_serializes_oauth_resource --lib`
- `cargo test -p codex-core config_schema_matches_fixture --lib`

## Notes
Broader local package runs still hit unrelated pre-existing stack
overflows in:
- `codex-app-server::in_process_start_clamps_zero_channel_capacity`
-
`codex-core::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
2026-05-14 11:52:43 -07:00
Casey Chow
4a1f1df8ce [codex] fix plugin CLI active user layer compile (#22666)
## Why

PR #21396 merged after #17141 removed the old
`ConfigLayerStack::get_user_layer()` API. The new plugin CLI call sites
still used that stale API, which caused `main` to fail compilation.

## What Changed

- update `codex plugin marketplace list` to read configured marketplaces
through `get_active_user_layer()`
- update the plugin snapshot validation helper to use
`get_active_user_layer()`

This preserves the intended active writable user-layer behavior from the
profile-aware config API while fixing the stale call sites.

## Validation

- `cargo check -p codex-cli`
- `cargo test -p codex-cli --test plugin_cli`
- `git diff --check`
2026-05-14 18:41:04 +00:00
Rajeev Nayak
f13e21ef43 Prefer the model list fetched from the backend for SIWC users (#22547)
## Summary
- For SIWC users, update the model list merging logic to prefer the
model list fetched from the backend over the bundled model list (this is
needed for special cases where users have a more limited set of models
they're allowed to use)
- Add or update tests covering the revised cache behavior

## Testing
- Added/updated unit tests in
`codex-rs/models-manager/src/manager_tests.rs`
- Not run (not requested)
2026-05-14 13:45:49 -04:00
Felipe Coury
5a02962519 fix(tui): render network approval history by target (#22229)
## Why

Network approval prompts are rendered without a command string on the
app-server path. After the user approves one of those prompts, the TUI
history cell previously fell back to command-oriented copy and produced
malformed lines such as:

```text
You approved codex to run  every time this session
```

That hid the network target the user actually approved and left a
visibly broken transcript entry.

## What changed

- Preserve the approval subject as either a command or a network target
when recording TUI approval decisions.
- Render target-aware history copy for network approval outcomes:
  - approve once
  - approve for the current session
  - cancel
- Include the approval protocol and preserve the managed-proxy
`network-access` target when present, including non-default ports such
as `https://example.com:8443`.
- Fall back to formatting the network approval context as
`protocol://host` when no generated target command is available.
- Keep ordinary command approval history, Guardian approval history, and
persisted network-rule history behavior unchanged.
- Add focused regression coverage and snapshots for the three
network-history cases.

## How to Test

1. Start Codex in a flow that triggers a network approval prompt.
2. Approve network access only for the current conversation.
3. Confirm the transcript records the approved network target, for
example:
- `You approved codex network access to https://example.com:8443 every
time this session`
4. Trigger the prompt again and verify the one-time approval and cancel
paths also record target-specific history text instead of an empty
command gap.

Targeted automated coverage:
- `cargo test -p codex-tui network_exec_approval_history`

## Additional verification

- `cargo insta pending-snapshots`
- `git diff --check`
- `just fix -p codex-tui`
- `just argument-comment-lint`

## Known unrelated local test noise

A full `cargo test -p codex-tui` run still hits a pre-existing stack
overflow outside this change:
- `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`
aborts with a stack overflow
2026-05-14 14:33:54 -03:00
Chris Bookholt
6ec8c4a6ec [codex] Ignore fsmonitor config in Git metadata reads (#22652)
## Summary
- keep Git metadata/status subprocesses independent of repository
`core.fsmonitor` configuration
- preserve existing working-tree state reporting while making the helper
behavior more predictable
- add regression coverage for `get_has_changes` when a repository
defines an fsmonitor command

## Validation
- `cargo fmt --all`
- `cargo test -p codex-core test_get_has_changes_`
- `cargo test -p codex-git-utils`
2026-05-14 10:07:43 -07:00
starr-openai
8736e32657 tests: avoid ambient temp sandbox roots (#22576)
## Why
Some sandboxed integration tests enabled both ambient temp roots
(`TMPDIR` and literal `/tmp`) even though they were not testing
temp-root behavior. On Linux bwrap, making `/tmp` writable causes
protected metadata mount targets such as `/tmp/.git`, `/tmp/.agents`,
and `/tmp/.codex` to be synthesized. If a run is interrupted, those
top-level markers can be left behind and contaminate later tests.

## What changed
For the incidental integration tests that do not need ambient temp-root
access, set `exclude_tmpdir_env_var` and `exclude_slash_tmp` to `true`.
Dedicated protected-metadata coverage remains in the lower-level sandbox
tests that use isolated temp roots.

## Verification
Focused remote devbox repros passed with a watcher polling `/tmp/.git`,
`/tmp/.agents`, and `/tmp/.codex`; no leaked markers were observed.
2026-05-14 10:04:24 -07:00
Casey Chow
74a1b46a00 [codex] add plugin marketplace CLI commands (#21396)
## Why

Plugin CLI installs should behave more like `apt-get install`:
configured marketplaces are the only install sources, the local
marketplace snapshot is the package index used at install time, and
`plugins/cache` is only a cache of already-downloaded plugin bytes.

That distinction matters once marketplaces and plugins have auth or
availability state. A repo-local marketplace manifest or leftover cached
plugin artifact should not silently become an install source unless the
marketplace was explicitly configured and its readable snapshot still
authorizes the plugin.

## What Changed

- add CLI commands to list configured marketplaces and add, list, or
remove marketplace plugins
- accept stable `plugin@marketplace` ids for add/remove while preserving
the explicit `--marketplace` form
- restrict `codex plugin add` and `codex plugin list` to configured
marketplaces instead of also discovering current-working-directory
marketplace roots
- fail `codex plugin add` and `codex plugin list` when a configured
marketplace snapshot is missing or malformed instead of treating it as
an empty source or a generic plugin miss
- preserve marketplace snapshot semantics: a configured local/Git
marketplace snapshot can authorize installs without consulting the
original upstream source
- allow `plugins/cache` reuse only after configured marketplace
resolution succeeds
- keep removal resilient after marketplace deletion or drift and ignore
malformed marketplace config entries in listing

## Commands Added

- `codex plugin add <plugin>@<marketplace>`
- `codex plugin add <plugin> --marketplace <marketplace>`
- `codex plugin list`
- `codex plugin list --marketplace <marketplace>`
- `codex plugin remove <plugin>@<marketplace>`
- `codex plugin remove <plugin> --marketplace <marketplace>`
- `codex plugin marketplace add <source>`
- `codex plugin marketplace add <source> --ref <ref>`
- `codex plugin marketplace add <source> --sparse <path>`
- `codex plugin marketplace list`
- `codex plugin marketplace upgrade`
- `codex plugin marketplace upgrade <marketplace>`
- `codex plugin marketplace remove <marketplace>`

## CLI Help Output

<details>
<summary><code>codex plugin --help</code></summary>

```text
Manage Codex plugins

Usage: codex plugin [OPTIONS] <COMMAND>

Commands:
  add          Install a plugin from a configured marketplace snapshot
  list         List plugins available from configured marketplace snapshots
  marketplace  Add, list, upgrade, or remove configured plugin marketplaces
  remove       Remove an installed plugin from local config and cache
  help         Print this message or the help of the given subcommand(s)
```

</details>

<details>
<summary><code>codex plugin add --help</code></summary>

```text
Install a plugin from a configured marketplace snapshot.

Pass either `PLUGIN@MARKETPLACE` or pass `PLUGIN` with `--marketplace MARKETPLACE`.

Usage: codex plugin add [OPTIONS] <PLUGIN[@MARKETPLACE]>

Arguments:
  <PLUGIN[@MARKETPLACE]>
          Plugin selector to install: either PLUGIN@MARKETPLACE or PLUGIN with --marketplace

Options:
  -m, --marketplace <MARKETPLACE>
          Configured marketplace name to use when PLUGIN does not include @MARKETPLACE

Examples:
  codex plugin add sample@debug
  codex plugin add sample --marketplace debug
```

</details>

<details>
<summary><code>codex plugin list --help</code></summary>

```text
List plugins available from configured marketplace snapshots

Usage: codex plugin list [OPTIONS]

Options:
  -m, --marketplace <MARKETPLACE>
          Only list plugins from this configured marketplace name

Examples:
  codex plugin list
  codex plugin list --marketplace debug
```

</details>

<details>
<summary><code>codex plugin remove --help</code></summary>

```text
Remove an installed plugin from local config and cache.

Pass either `PLUGIN@MARKETPLACE` or pass `PLUGIN` with `--marketplace MARKETPLACE`.

Usage: codex plugin remove [OPTIONS] <PLUGIN[@MARKETPLACE]>

Arguments:
  <PLUGIN[@MARKETPLACE]>
          Plugin selector to remove: either PLUGIN@MARKETPLACE or PLUGIN with --marketplace

Options:
  -m, --marketplace <MARKETPLACE>
          Marketplace name to use when PLUGIN does not include @MARKETPLACE

Examples:
  codex plugin remove sample@debug
  codex plugin remove sample --marketplace debug
```

</details>

<details>
<summary><code>codex plugin marketplace --help</code></summary>

```text
Add, list, upgrade, or remove configured plugin marketplaces

Usage: codex plugin marketplace [OPTIONS] <COMMAND>

Commands:
  add      Add a local or Git marketplace to the configured marketplace sources
  list     List configured marketplace names and their local snapshot roots
  upgrade  Refresh configured Git marketplace snapshots
  remove   Remove a configured marketplace source by name
```

</details>

<details>
<summary><code>codex plugin marketplace add --help</code></summary>

```text
Add a local or Git marketplace to the configured marketplace sources

Usage: codex plugin marketplace add [OPTIONS] <SOURCE>

Arguments:
  <SOURCE>
          Marketplace source: a local path, owner/repo[@ref], HTTPS Git URL, or SSH Git URL

Options:
      --ref <REF>
          Git ref to fetch for Git marketplace sources

      --sparse <PATH>
          Sparse checkout path for Git marketplace sources. Can be repeated

Examples:
  codex plugin marketplace add ./path/to/marketplace
  codex plugin marketplace add owner/repo --ref main
  codex plugin marketplace add https://github.com/owner/repo --sparse plugins/foo
```

</details>

<details>
<summary><code>codex plugin marketplace list --help</code></summary>

```text
List configured marketplace names and their local snapshot roots

Usage: codex plugin marketplace list [OPTIONS]
```

</details>

<details>
<summary><code>codex plugin marketplace upgrade --help</code></summary>

```text
Refresh configured Git marketplace snapshots.

Omit MARKETPLACE_NAME to upgrade all configured Git marketplaces.

Usage: codex plugin marketplace upgrade [OPTIONS] [MARKETPLACE_NAME]

Arguments:
  [MARKETPLACE_NAME]
          Optional configured marketplace name to upgrade. Omit to upgrade all Git marketplaces

Examples:
  codex plugin marketplace upgrade
  codex plugin marketplace upgrade debug
```

</details>

<details>
<summary><code>codex plugin marketplace remove --help</code></summary>

```text
Remove a configured marketplace source by name

Usage: codex plugin marketplace remove [OPTIONS] <MARKETPLACE_NAME>

Arguments:
  <MARKETPLACE_NAME>
          Configured marketplace name to remove

Example:
  codex plugin marketplace remove debug
```

</details>

## Public Semantics

- `codex plugin add <plugin>@<marketplace>` succeeds only when
`<marketplace>` is configured and its local marketplace snapshot
contains `<plugin>`
- repo-local marketplaces are not install sources until the user runs
`codex plugin marketplace add ...`
- configured marketplace snapshots must be readable; missing or
malformed snapshots fail the CLI operation rather than silently falling
through to cache or empty results
- cached plugin artifacts can satisfy reinstall only when the configured
marketplace snapshot still authorizes that plugin
- cached plugin artifacts alone never make a plugin installable

## Tests

- `cargo test -p codex-cli --test plugin_cli`
- `cargo clippy -p codex-cli --tests -- -D warnings`
- `cargo test -p codex-cli`
- `git diff --check`
- `just bazel-lock-update`
- `just bazel-lock-check`
2026-05-14 09:33:38 -07:00
Eric Traut
a5040d0b39 tui: split composer attachment and popup state (#22581)
## Why

`ChatComposer` currently owns text editing alongside attachment
bookkeeping and popup lifecycle state, while `BottomPane` still triggers
a couple of popup resyncs after composer methods that already do that
work internally. That blurs the ownership boundary and makes the
composer harder to simplify safely.

This PR is part 1 of a two-part cleanup. It peels off the composer state
that can move cleanly on its own, so the follow-up can tackle the
heavier draft/editing boundary without mixing every concern into one
diff.

## What changed

- Move local and remote image bookkeeping, placeholder relabeling, and
remote-image keyboard selection into `AttachmentState`.
- Move active-popup and popup-dismissal/query bookkeeping into
`PopupState`.
- Update composer and history-search paths to use those state owners
directly.
- Remove redundant `BottomPane` popup synchronization after paste
handling and `insert_str`.

## Part 2

The follow-up PR will finish the cleanup around the remaining composer
boundary: split out the draft/editing-oriented state and footer/status
presentation concerns that still live in `ChatComposer`, then revisit
the leftover `BottomPane` pass-throughs once those ownership lines are
explicit. The goal is for `ChatComposer` to coordinate a few focused
collaborators instead of continuing to be the landing zone for every
input-path concern.

## Verification

Did manual smoke tests.
2026-05-14 09:04:27 -07:00
Shijie Rao
e79e1b42b9 Chore: better published unsigned artifacts (#22649)
This is the exact same change as @bolinfest made but he could not push
because of github action change permission.

## Why

The `rust-release` workflow can now be run manually with
`sign_macos=false` to skip macOS signing, but that path previously
stopped before creating a GitHub Release. That left the unsigned macOS
binaries available only as workflow-run artifacts, which are awkward to
fetch from automation and cannot be retrieved with a simple
unauthenticated `curl`.

For the unsigned path we still should not perform the normal release
side effects: no npm or Python publishing, no WinGet publishing, no
`latest-alpha-cli` branch update, and no promotion to GitHub's latest
release. The goal is only to make the build outputs easy to fetch from
the release page.

## What changed

- Allow the `release` job in `.github/workflows/rust-release.yml` to run
for `workflow_dispatch` runs with `sign_macos=false`.
- For unsigned runs, keep the unsigned macOS artifacts plus the normal
Linux and Windows release artifacts needed for DotSlash, then
create/update the GitHub Release with `make_latest: false`.
- Keep the normal publish/promote paths gated to signed releases:
  - npm staging and publish
  - Python runtime publish
  - WinGet publish
  - `latest-alpha-cli` update
  - developer-site deploy
  - normal DotSlash release files
- Add `.github/dotslash-unsigned-config.json`, which publishes
`*-unsigned` DotSlash files that use unsigned macOS artifacts and the
normal Linux/Windows artifacts.


## What I added
PLEASE READ THIS!!!
I added `codex-command-runner` and `codex-windows-sandbox-setup` entries
to `.github/dotslash-unsigned-config.json` so that with
`sign_macos=false` we would still get the dotslash files for those
artifacts which are necessary for windows builds.
2026-05-14 08:47:21 -07:00
Michael Bolin
01d93fd9fc permissions: canonicalize workspace_roots and danger-full-access names (#22624)
## Why

This is a small precursor to the larger permissions-migration work. Both
the comparison stack in
[#22401](https://github.com/openai/codex/pull/22401) /
[#22402](https://github.com/openai/codex/pull/22402) and the alternate
stack in [#22610](https://github.com/openai/codex/pull/22610) /
[#22611](https://github.com/openai/codex/pull/22611) /
[#22612](https://github.com/openai/codex/pull/22612) are easier to
review if the terminology is already settled underneath them.

Because `:project_roots` and `:danger-no-sandbox` have not shipped as
stable user-facing surface area, carrying them forward as aliases would
just add more migration logic to the later stacks. This PR removes that
ambiguity now so the follow-on work can rely on one spelling for each
built-in concept.

## What Changed

- renamed the config-facing special filesystem key from `:project_roots`
to `:workspace_roots`
- dropped unpublished `:project_roots` parsing support in
`core/src/config/permissions.rs`, so new config only recognizes
`:workspace_roots`
- renamed the built-in full-access permission profile id from
`:danger-no-sandbox` to `:danger-full-access`
- dropped unpublished `:danger-no-sandbox` support entirely, including
the old active-profile canonicalization path, and added explicit
rejection coverage for the legacy id
- introduced shared built-in permission-profile id constants in
`codex-rs/protocol/src/models.rs`
- updated `core`, `app-server`, and `tui` call sites that special-case
built-in profiles to use the shared constants and canonical ids
- updated tests and the Linux sandbox README to use `:workspace_roots` /
`:danger-full-access`

## Verification

I focused verification on the three places this rename can regress:
config parsing, active-profile identity surfaced back out of `core`, and
user/server call sites that special-case built-in profiles.

Targeted checks:

-
`config::tests::default_permissions_can_select_builtin_profile_without_permissions_table`
-
`config::tests::default_permissions_read_only_applies_additional_writable_roots_as_modifications`
-
`config::tests::default_permissions_can_select_builtin_full_access_profile`
- `config::tests::legacy_danger_no_sandbox_is_rejected`
- `workspace_root` filtered `codex-core` tests
-
`request_processors::thread_processor::thread_processor_tests::thread_processor_behavior_tests::requested_permissions_trust_project_uses_permission_profile_intent`
-
`suite::v2::turn_start::turn_start_rejects_invalid_permission_selection_before_starting_turn`
- `status::tests::status_snapshot_shows_auto_review_permissions`
-
`status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access`
-
`app_server_session::tests::embedded_turn_permissions_use_active_profile_selection`
2026-05-14 08:45:54 -07:00
jif-oai
12bfb57139 Fix turn extension data task plumbing (#22646)
## Summary
- carry the per-turn extension data through RunningTask so abort
handling can rebuild SessionTaskContext
- update stale test ExtensionData::new() callsites to pass the turn id

## Testing
- Not run after PR branch creation; CI will cover.
2026-05-14 16:00:06 +02:00
Chris Bookholt
9ea38136b0 [codex] treat PowerShell stop-parsing forms as unsupported (#22643)
## Summary
- Treat PowerShell stop-parsing token forms as unsupported in the
AST-backed command flattener.
- Add focused regressions at the parser layer and Windows command-safety
layer.

## Why
The command-safety parser lowers PowerShell AST elements into argv-like
words. Stop-parsing syntax preserves a native-command argument shape
that this lowering does not model, so these forms should stay on the
conservative unsupported path.

## Validation
- `cargo fmt --manifest-path codex-rs/Cargo.toml --all --check`
- `cargo test --manifest-path codex-rs/Cargo.toml -p
codex-shell-command`
2026-05-14 06:28:34 -07:00
jif-oai
deedf3b2c4 feat: add layered --profile-v2 config files (#17141)
## Why

`--profile-v2 <name>` gives launchers and runtime entry points a named
profile config without making each profile duplicate the base user
config. The base `$CODEX_HOME/config.toml` still loads first, then
`$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
writable user config for that session.

That keeps shared defaults, plugin/MCP setup, and managed/user
constraints in one place while letting a named profile override only the
pieces that need to differ.

## What Changed

- Added the shared `--profile-v2 <name>` runtime option with validated
plain names, now represented by `ProfileV2Name`.
- Extended config layer state so the base user config and selected
profile config are both `User` layers; APIs expose the active user layer
and merged effective user config.
- Threaded profile selection through runtime entry points: `codex`,
`codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
debug prompt-input`.
- Made user-facing config writes go to the selected profile file when
active, including TUI/settings persistence, app-server config writes,
and MCP/app tool approval persistence.
- Made plugin, marketplace, MCP, hooks, and config reload paths read
from the merged user config so base and profile layers both participate.
- Updated app-server config layer schemas to mark profile-backed user
layers.

## Limits

`--profile-v2` is still rejected for config-management subcommands such
as feature, MCP, and marketplace edits. Those paths remain tied to the
base `config.toml` until they have explicit profile-selection semantics.

Some adjacent background writes may still update base or global state
rather than the selected profile:

- marketplace auto-upgrade metadata
- automatic MCP dependency installs from skills
- remote plugin sync or uninstall config edits
- personality migration marker/default writes

## Verification

Added targeted coverage for profile name validation, layer
ordering/merging, selected-profile writes, app-server config writes,
session hot reload, plugin config merging, hooks/config fixture updates,
and MCP/app approval persistence.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-05-14 15:16:15 +02:00
jif-oai
17cd321c32 Wire turn item contributors into stream output (#22494)
## Summary
- run registered TurnItemContributor hooks for parsed stream output
items
- plumb the active turn extension store into stream item handling
- preserve existing memory citation parsing as fallback after
contributors run

## Tests
- cargo test -p codex-core stream_events_utils -- --nocapture
- just fmt
- just fix -p codex-core
- git diff --check
2026-05-14 14:48:17 +02:00
jif-oai
6d65686313 feat: make ToolExecutor an async trait (#22560)
## Why

`codex_tools::ToolExecutor` keeps a tool spec attached to its runtime
handler, but extension tools still carried a parallel
`ExtensionToolFuture` / `ExtensionToolExecutor` shape. That made
extension-owned tools look different from host tools even though
routing, registration, and execution need the same abstraction.

This PR makes the shared executor contract directly async and lets
extension tools implement it too, so host tools and extension tools can
move through the same registration path.

## What changed

- Changed `ToolExecutor::handle` to an `async fn` using `async-trait`,
and updated built-in tool handlers to implement the async trait
directly.
- Replaced the bespoke `ExtensionToolFuture` contract with a marker
`ExtensionToolExecutor` over `ToolExecutor<ToolCall, Output =
JsonToolOutput>`, re-exporting `ToolExecutor` from
`codex-extension-api`.
- Updated the memories extension tools to implement the shared executor
trait.
- Split tool-router construction into collected executors plus hosted
model specs, keeping hosted tools like web search and image generation
separate from executable handlers.
- Updated spec/router tests and extension-tool stubs for the new
executor shape.

## Verification

- Not run locally.
2026-05-14 11:23:57 +02:00