## 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
## 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`
## 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`
## 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.
## 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.
## 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
```
## 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`
## 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`
## 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)
## 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
## 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`
## 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.
## 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`
## 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.
## 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`
## 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.
## 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`
## 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>
## 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
## 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.
## Why
This is a follow-up to #22573. This problem was surfaced in a code
review comment that I missed before merging the previous PR.
Fresh-session startup could prepare a model-availability NUX before
`app_server.start_thread(&config)` completed. If thread startup then
failed, the TUI never rendered the tooltip, but
`prepare_startup_tooltip_override(...)` had already persisted one of the
limited impressions.
## What Changed
- Move startup tooltip preparation inside the fresh-thread startup
branch, after `start_thread(...)` succeeds.
- Keep resume/fork paths unchanged.
- Remove the now-redundant
`should_prepare_startup_tooltip_override(...)` helper and its gate test.
## Summary
- Allow remote installed-plugin cache refresh to start whenever plugins
are enabled.
- Allow remote installed-plugin bundle sync to start whenever plugins
are enabled.
- Remove the extra local `remote_plugin_enabled` guard from those
background sync paths.
## Context
Server-side installed plugin state and optional bundle URL behavior are
owned by plugin-service `/public/plugins/installed`, so these local sync
paths only need the overall plugin enablement gate.
## Test plan
- `just fmt`
- `cargo test -p codex-core-plugins`
## Why
The TUI startup test surface had drifted into expensive, brittle
coverage:
- `tui/tests/suite/no_panic_on_startup.rs` was already ignored as flaky
while still spawning a PTY to exercise malformed exec-policy rules.
- `tui/tests/suite/model_availability_nux.rs` used a seeded session,
cursor-query spoofing, and repeated interrupts to verify a narrow
resume-path invariant.
- `app/tests.rs` had started accumulating unrelated startup and summary
coverage in one flat module even after the surrounding app code was
split into feature modules.
This keeps those behaviors covered while making the tests cheaper to
understand and less likely to rot. It also preserves the malformed-rules
regression from #8803 without requiring a terminal orchestration test.
## What changed
- Replaced the malformed `rules` startup PTY case with a direct
exec-policy loader regression:
[`rules_path_file_returns_read_dir_error`](21b6b5622f/codex-rs/core/src/exec_policy_tests.rs (L264-L284))
- Made the existing fresh-session-only startup tooltip behavior explicit
with
[`should_prepare_startup_tooltip_override`](21b6b5622f/codex-rs/tui/src/app/thread_routing.rs (L1272-L1279)),
then added focused coverage for the resume/fork gate and the persisted
NUX counter.
- Split startup and session-summary coverage out of
`tui/src/app/tests.rs` into dedicated modules so the test layout better
mirrors the current app architecture.
- Converted one single-message goal validation snapshot into semantic
assertions where layout was not the behavior under test.
- Removed the two PTY-heavy suite files that the narrower tests now
supersede.
## Verification
- `cargo test -p codex-core rules_path_file_returns_read_dir_error`
- `cargo test -p codex-tui startup_`
- `cargo test -p codex-tui session_summary_`
- `cargo test -p codex-tui
goal_slash_command_rejects_oversized_objective`
## Why
reapplies https://github.com/openai/codex/pull/22386 which was
previously reverted
Also, introduce `remoteControl/enable` and `remoteControl/disable`
app-server APIs to toggle on/off remote control at runtime for a given
running app-server instance.
## What Changed
- Adds experimental v2 RPCs:
- `remoteControl/enable`
- `remoteControl/disable`
- Adds `RemoteControlRequestProcessor` and routes the new RPCs through
it instead of `ConfigRequestProcessor`.
- Adds named `RemoteControlHandle::enable`, `disable`, and `status`
methods.
- Makes `remoteControl/enable` return an error when sqlite state DB is
unavailable, while keeping enrollment/websocket failures as async status
updates.
- Adds `AppServerRuntimeOptions.remote_control_enabled` and hidden
`--remote-control` flags for `codex app-server` and `codex-app-server`.
- Updates managed daemon startup to use `codex app-server
--remote-control --listen unix://`.
- Marks `Feature::RemoteControl` as removed and ignores
`[features].remote_control`.
- Updates app-server README entries for the new remote-control methods.
## Why
`codex remote-control` manages the app-server daemon with
`remote_control` enabled, but it previously only exposed an implicit
start path. Once started, there was no obvious top-level
`remote-control` command for stopping the daemon; users had to know
about the lower-level `codex app-server daemon stop` command.
The startup failure for missing managed installs was also ambiguous.
`codex remote-control` and daemon bootstrap require the standalone Codex
install under `CODEX_HOME/packages/standalone/current/codex`, but the
old error only said to install Codex first, which is unclear when
another `codex` binary is already on PATH. Now we add an explicit
instruction for how to get the standalone Codex install.
## What changed
- Converts `codex remote-control` into a command group while preserving
bare `codex remote-control` as the existing start behavior.
- Adds `codex remote-control start` as the explicit start path.
- Adds `codex remote-control stop`, which maps to app-server daemon
stop.
- Updates the shared daemon managed-install error to name the missing
standalone path, explain why that install is required, provide the
installer command, and tell users to rerun the command they just tried.
## Verification
- `cargo test -p codex-app-server-daemon`
- `cargo test -p codex-cli`
- `./target/debug/codex remote-control --help`
## Summary
Get rid of the `experimental_use_freeform_apply_patch` config option,
since it is now encoded in model config. No deprecation message since it
has been experimental this entire time.
## Testing
- [x] Updated unit tests
---------
Co-authored-by: Codex <noreply@openai.com>
All apps must be able to open the db to proceed -- codex is having
issues with manufacturing new installation ids in local mode when the db
can't be opened for race conditions or any other reasons.
Remove unnecessary prefix filtering from codex
## Test Plan
Test local cli build + make sure backend returns appropriate apps
```
cd ~/code/codex/codex-rs
cargo build -p codex-cli --bin codex
./target/debug/codex
```
Appropriate apps show up in my list
## Why
`chatwidget.rs` is still carrying too many unrelated responsibilities in
one file. #22269 started a five-phase cleanup to move coherent behavior
domains into focused modules while keeping `chatwidget.rs` as the
composition layer. #22407 completed phase 2 by extracting input and
submission flow, #22433 completed phase 3 by extracting protocol,
replay, streaming, and tool lifecycle handling, and #22518 completed
phase 4 by extracting settings, popups, and status surfaces.
This PR is phase 5. It cleans up the remaining constructor and
orchestration code now that the larger behavior domains have moved out,
leaving `chatwidget.rs` much closer to the composition layer the cleanup
was aiming for. This is once again a mechanical movement of existing
functions. No functional changes.
## What Changed
- Added focused modules for widget construction and initial wiring,
session configuration flow, key/composer interaction routing, review
popup orchestration, desktop notification coalescing, and render
composition.
- Moved the remaining constructor, session setup, interaction,
notification, review picker, and rendering helpers out of
`codex-rs/tui/src/chatwidget.rs`.
- Preserved the existing startup/session behavior, keyboard handling,
review picker flow, notification priority behavior, and render
composition while shrinking the central widget module substantially.
- Left `codex-rs/tui/src/chatwidget.rs` as the registration and
composition surface for the extracted behavior modules.
## Cleanup Phases
The five-phase cleanup plan from #22269 is:
1. Phase 1: mechanical helper and state moves. Completed in #22269.
2. Phase 2: extract input and submission flow, including queued user
messages, shell prompt submission, pending steer restoration, and thread
input snapshot/restore behavior. Completed in #22407.
3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
handling, while preserving active-cell grouping, transcript
invalidation, interrupt deferral, and final-message separator behavior.
Completed in #22433.
4. Phase 4: extract settings, popups, and status surfaces, including
model/reasoning/collaboration/personality popups, permission prompts,
rate-limit UI, and connectors helpers. Completed in #22518.
5. Phase 5: clean up the remaining constructor and orchestration code
once the larger behavior domains have moved out, leaving `chatwidget.rs`
as the composition layer. This PR.
## Verification
- `cargo check -p codex-tui`
- `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
- `cargo test -p codex-tui chatwidget::tests::plan_mode`
- `cargo test -p codex-tui chatwidget::tests::review_mode`
- `cargo test -p codex-tui chatwidget::tests::status_and_layout`
`cargo test -p codex-tui` also compiles and begins running, but aborts
in the unchanged app-side test
`app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
with the same reproducible stack overflow noted in phase 4.
## Summary
`/collab` was intentionally removed in
[#12012](https://github.com/openai/codex/pull/12012), but the
TUI/app-server migration accidentally brought that slash-command path
back. This restores the earlier product decision so the TUI no longer
advertises or dispatches `/collab`. This command was redundant because
it did the same thing as `/plan` but in a less-intuitive way.
## What Changed
- Remove `SlashCommand::Collab` from the TUI slash-command surface.
- Delete the picker and app-event plumbing that only existed to service
`/collab`.
- Remove obsolete TUI test coverage for the deleted picker flow.
# Why
`PreToolUse.additionalContext` became model-visible after #20692, but
the hook-output spilling path from #21069 never picked up that newer
lane. As a result, oversized `PreToolUse` context could bypass the
truncation/spill treatment that already applies to the other hook
outputs Codex forwards to the model.
# What
- Run `PreToolUseOutcome.additional_contexts` through
`maybe_spill_texts(...)`
- Add an integration test proving a large `PreToolUse.additionalContext`
is replaced with a truncated preview plus spill-file pointer, while the
full text is preserved on disk.
## Why
`multi_agent_v2` already allowed configuring the minimum `wait_agent`
timeout, but the default timeout and upper bound were still hard-coded.
That made it hard to tune waits for subagent mailbox activity in
sessions that need either faster wakeups or longer waits, and it meant
the model-visible `wait_agent` schema could not fully reflect the
resolved runtime limits.
## What Changed
- Added `features.multi_agent_v2.max_wait_timeout_ms` and
`features.multi_agent_v2.default_wait_timeout_ms` alongside the existing
`min_wait_timeout_ms` setting.
- Validated all three timeouts in config as `0..=3_600_000`, with
`min_wait_timeout_ms <= default_wait_timeout_ms <= max_wait_timeout_ms`.
- Thread and review session tool config now passes the resolved
min/default/max values into the `wait_agent` tool schema.
- `wait_agent` now uses the configured default when `timeout_ms` is
omitted and rejects explicit values outside the configured min/max range
instead of silently clamping them.
- Updated the generated config schema and config-lock test coverage for
the new fields.
## Why
On Windows, elevated sandboxed commands run under a dedicated sandbox
account while `HOME` / `USERPROFILE` can still point at the real user's
profile directory. For PowerShell login shells, that combination can
make the sandbox account try to load the real user's PowerShell profile
script. If the sandbox account's execution policy differs from the real
user's policy, startup can emit profile-loading errors before the
requested command runs.
For this backend, loading the profile is not a faithful user login
shell: it is cross-account profile execution. Treating these PowerShell
invocations as non-login shells avoids that invalid startup path.
## Why This Happens Late
The normal `login` decision is resolved when shell argv is created, but
that point is too early to make this Windows sandbox-specific decision.
At argv creation time we do not yet know the actual sandbox attempt that
will run the command. A turn can include sandboxed and unsandboxed
attempts, and a broad turn-level override would also affect Full Access
commands where the user's profile should remain available.
Instead, this change carries the selected `ShellType` alongside the argv
and applies the `-NoProfile` adjustment in the shell runtimes once the
`SandboxAttempt` is known. That keeps the override scoped to actual
`WindowsRestrictedToken` attempts with `WindowsSandboxLevel::Elevated`.
The runtime uses the selected shell metadata rather than re-detecting
PowerShell from argv. That avoids brittle parsing and covers PowerShell
invocation shapes such as `-EncodedCommand`.
## What Changed
- Carry selected shell metadata through `exec_command` / unified exec
requests and shell tool requests.
- Insert `-NoProfile` for PowerShell commands only when the runtime is
about to execute a sandboxed elevated Windows attempt.
- Add focused unit coverage for elevated Windows PowerShell,
`-EncodedCommand`, existing `-NoProfile`, legacy restricted-token
attempts, unsandboxed attempts, and non-PowerShell commands.
## Verification
- `cargo test -p codex-core disable_powershell_profile_tests`
- `cargo test -p codex-core test_get_command`
- `cargo clippy --fix --tests --allow-dirty --allow-no-vcs -p
codex-core`
A full `cargo test -p codex-core` run was also attempted during
development, but it still hit an unrelated stack overflow in
`agent::control` tests before reaching this area.
## Why
Users and support need a single command that captures the local Codex
runtime, configuration, auth, terminal, network, and state shape without
asking the user to know which diagnostic depth to choose first. `codex
doctor` now runs the useful checks by default and makes the detailed
human output the default because the command is usually run when someone
already needs context.
The command also targets concrete support failure modes we have seen
while iterating on the design:
- update-target mismatches like #21956, where the installed package
manager target can differ from the running executable
- terminal and multiplexer issues that depend on `TERM`, tmux/zellij
state, color handling, and TTY metadata
- provider-specific HTTP/WebSocket connectivity, including ChatGPT
WebSocket handshakes and API-key/provider endpoint reachability
- local state/log SQLite integrity problems and large rollout
directories
- feedback reports that need an attached, redacted diagnostic snapshot
without asking the user to run a second command
## What Changed
- Adds `codex doctor` as a grouped CLI diagnostic report with default
detailed output and `--summary` for the compact view.
- Adds stable report sections for Environment, Configuration, Updates,
Connectivity, and Background Server, plus a top Notes block that
promotes anomalies such as available updates, large rollout directories,
optional MCP issues, and mixed auth signals.
- Adds runtime provenance, install consistency, bundled/system search
readiness, terminal/multiplexer metadata, `config.toml` parse status,
auth mode details, sandbox details, feature flag summaries, update
cache/latest-version state, app-server daemon state, SQLite integrity
checks, rollout statistics, and provider-aware network diagnostics.
- Adds ChatGPT WebSocket diagnostics that report the negotiated HTTP
upgrade as `HTTP 101 Switching Protocols` and include timeout, DNS,
auth, and provider context in detailed output.
- Makes reachability provider-aware: API-key OpenAI setups check the API
endpoint, ChatGPT auth checks the ChatGPT path, and custom/AWS/local
providers check configured HTTP endpoints when available.
- Adds structured, redacted JSON output where `checks` is keyed by check
id and `details` is a key/value object for support tooling.
- Integrates doctor with feedback uploads by attaching a best-effort
`codex-doctor-report.json` report and adding derived Sentry tags for
overall status and failing/warning checks.
- Updates the TUI feedback consent copy so users can see that the doctor
report is included when logs/diagnostics are uploaded.
- Updates the CLI bug issue template to ask reporters for `codex doctor
--json` and render pasted reports as JSON.
## Example Output
The examples below are sanitized from local smoke runs with `--no-color`
so the structure is reviewable in plain text.
### `codex doctor`
```text
Codex Doctor v0.0.0 · macos-aarch64
Notes
↑ updates 0.130.0 available (current 0.0.0, dismissed 0.128.0)
⚠ rollouts 1,526 active files · 2.53 GB on disk
⚠ mcp MCP configuration has optional issues
⚠ auth mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
─────────────────────────────────────────────────────────────
Environment
✓ runtime local debug build
version 0.0.0
install method other
commit unknown
executable ~/code/codex.fcoury-doct…x-rs/target/debug/codex
✓ install consistent
context other
managed by npm: no · bun: no · package root —
PATH entries (2) ~/.local/share/mise/installs/node/24/bin/codex
~/.local/share/mise/shims/codex
✓ search ripgrep 15.1.0 (system, `rg`)
✓ terminal Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
terminal Ghostty
TERM_PROGRAM ghostty
terminal version 1.3.2-main-+b0f827665
TERM xterm-256color
multiplexer tmux 3.6a
tmux extended-keys on
tmux allow-passthrough on
tmux set-clipboard on
✓ state databases healthy
CODEX_HOME ~/.codex (dir)
state DB ~/.codex/state_5.sqlite (file) · integrity ok
log DB ~/.codex/logs_2.sqlite (file) · integrity ok
active rollouts 1,526 files · 2.53 GB (avg 1.70 MB)
archived rollouts 8 files · 3.84 MB (avg 491.11 KB)
Configuration
✓ config loaded
model gpt-5.5 · openai
cwd ~/code/codex.fcoury-doctor/codex-rs
config.toml ~/.codex/config.toml
config.toml parse ok
MCP servers 1
feature flags 36 enabled · 7 overridden (full list with --all)
overrides code_mode, code_mode_only, memories, chronicle, goals, remote_control, prevent_idle_sleep
✓ auth auth is configured
auth storage mode File
auth file ~/.codex/auth.json
auth env vars present OPENAI_API_KEY
stored auth mode chatgpt
stored API key false
stored ChatGPT tokens true
stored agent identity false
⚠ mcp MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
configured servers 1
disabled servers 0
streamable_http servers 1
optional reachability openaiDeveloperDocs: https://developers.openai.com/mcp (HEAD connect failed; GET connect failed)
✓ sandbox restricted fs + restricted network · approval OnRequest
approval policy OnRequest
filesystem sandbox restricted
network sandbox restricted
Connectivity
✓ network network-related environment looks readable
✓ websocket connected (HTTP 101 Switching Protocols) · 15s timeout
model provider openai
provider name OpenAI
wire API responses
supports websockets true
connect timeout 15000 ms
auth mode chatgpt
endpoint wss://chatgpt.com/backend-api/<redacted>
DNS 2 IPv4, 2 IPv6, first IPv6
handshake result HTTP 101 Switching Protocols
✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
reachability mode API key auth
openai API https://api.openai.com/v1 connect failed (required)
Background Server
○ app-server not running (ephemeral mode)
─────────────────────────────────────────────────────────────
11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
--summary compact output --all expand truncated lists
--json redacted report
```
### `codex doctor --summary`
```text
Codex Doctor v0.0.0 · macos-aarch64
Notes
↑ updates 0.130.0 available (current 0.0.0, dismissed 0.128.0)
⚠ rollouts 1,526 active files · 2.53 GB on disk
⚠ mcp MCP configuration has optional issues
⚠ auth mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
─────────────────────────────────────────────────────────────
Environment
✓ runtime local debug build
✓ install consistent
✓ search ripgrep 15.1.0 (system, `rg`)
✓ terminal Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
✓ state databases healthy
Configuration
✓ config loaded
✓ auth auth is configured
⚠ mcp MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
✓ sandbox restricted fs + restricted network · approval OnRequest
Updates
✓ updates update configuration is locally consistent
Connectivity
✓ network network-related environment looks readable
✓ websocket connected (HTTP 101 Switching Protocols) · 15s timeout
✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
Background Server
○ app-server not running (ephemeral mode)
─────────────────────────────────────────────────────────────
11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
Run codex doctor without --summary for detailed diagnostics.
--all expand truncated lists --json redacted report
```
### `codex doctor --json` shape
```json
{
"schema_version": 1,
"overall_status": "fail",
"checks": {
"runtime.provenance": {
"id": "runtime.provenance",
"category": "Environment",
"status": "ok",
"summary": "local debug build",
"details": {
"version": "0.0.0",
"install method": "other",
"commit": "unknown"
}
},
"sandbox.helpers": {
"id": "sandbox.helpers",
"category": "Configuration",
"status": "ok",
"summary": "restricted fs + restricted network · approval OnRequest",
"details": {
"approval policy": "OnRequest",
"filesystem sandbox": "restricted",
"network sandbox": "restricted"
}
}
}
}
```
### `/feedback` new sentry attachment
<img width="938" height="798" alt="CleanShot 2026-05-13 at 15 36 14"
src="https://github.com/user-attachments/assets/715e62e0-d7b4-4fea-a35a-fd5d5d33c4c0"
/>
### New section in CLI issue template
<img width="1164" height="435" alt="CleanShot 2026-05-13 at 15 47 24"
src="https://github.com/user-attachments/assets/9081dc25-a28c-4afa-8ba1-e299c2b4031d"
/>
## How to Test
1. Run `cargo run --bin codex -- doctor --no-color`.
2. Confirm the detailed report is the default and includes promoted
Notes, grouped sections, terminal details, state DB integrity, rollout
stats, provider reachability, WebSocket diagnostics, and app-server
status.
3. Run `cargo run --bin codex -- doctor --summary --no-color`.
4. Confirm the compact view keeps the same sections and summary counts
but omits detailed key/value rows.
5. Run `cargo run --bin codex -- doctor --json`.
6. Confirm the output is redacted JSON, `checks` is an object keyed by
check id, and each check's `details` is a key/value object.
7. Preview the CLI bug issue template and confirm the `Codex doctor
report` field appears after the terminal field, asks for `codex doctor
--json`, and renders pasted output as JSON.
8. Start a feedback flow that includes logs.
9. Confirm the upload consent copy lists `codex-doctor-report.json`
alongside the log attachments.
Targeted tests:
- `cargo test -p codex-cli doctor`
- `cargo test -p codex-app-server
doctor_report_tags_summarize_status_counts`
- `cargo test -p codex-feedback`
- `cargo test -p codex-tui feedback_view`
- `just argument-comment-lint`
- `git diff --check`
Fixes#20587, reported by @noeljackson.
This prevents the TUI wrapping code from panicking when `textwrap`
returns a borrowed slice that does not point into the original source
text. The fix follows the direction proposed by @misrtjakub in the issue
comment: validate the borrowed slice pointer range first, and fall back
to the existing owned-line mapper when the slice is external.
- Guards borrowed wrapped slices before converting pointer offsets into
byte ranges.
- Reuses the existing owned-line range recovery path for external
borrowed slices.
- Adds coverage for rejecting borrowed slices outside the source text.
End-user testing steps:
- Start Codex in TUI mode under a PTY wrapper that can inject stdin
after startup.
- Inject `\x1b[200~test message\x1b[201~\r` after the TUI is ready.
- Confirm Codex does not panic and the pasted text is handled normally.
Local validation:
- `cargo test -p codex-tui wrapping::tests::`
- `cargo test -p codex-tui -- --skip
status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access
--skip
status::tests::status_permissions_full_disk_managed_without_network_is_external_sandbox`
This switches TUI plugin mentions to use app-server `plugin/list` for
plugin inventory and metadata instead of `PluginManager`, while keeping
the same mention-eligibility filters as before.
Same filters as before:
- Only plugins in the current config / cwd scope.
- Only installed and enabled plugins.
- Only plugins that actually expose a capability, meaning at least one
skill, MCP server, or app connector.
- Uses `plugin/list` for the mention names/descriptions
# Why
Plugin-bundled hooks are already wired through the plugin manager,
session setup, and app-server hook listing paths. Keeping `plugin_hooks`
disabled by default means users still need an explicit feature opt-in
before that existing behavior participates in normal plugin loading.
# What
- mark `plugin_hooks` as stable and enable it by default
- add feature-registry test coverage for the new default/stage pairing
Validation:
- `cargo test -p codex-features`
- `just fmt`
## Summary
- Add a deterministic callback-id path segment to local MCP OAuth
redirect URIs before starting authorization.
- Derive the callback id from the normalized MCP server URL and encode
it as a 12-character URL-safe hash.
- Reuse the existing exact callback-path validation so OAuth completion
only succeeds on the callback path that was sent in the redirect URI.
## Context
Slack thread:
https://openai.slack.com/archives/C087WB3AGCR/p1777480566571699
That thread calls out the OAuth mix-up class of issue for MCP servers.
The connector/App Connect flow already has a callback_id concept that
binds the OAuth callback URL to the MCP app/server identity. Codex
desktop's local MCP OAuth flow was still using a generic local callback
path like `/callback`, so this PR adds the same shape to the shared
local MCP OAuth helper.
## Behavior
Before this change, local MCP OAuth used:
- default local callback URL: `http://127.0.0.1:<port>/callback`
- configured callback URL: `<configured callback URL>` unchanged
After this change, Codex appends a deterministic callback-id segment:
- default local callback URL:
`http://127.0.0.1:<port>/callback/<callback_id>`
- configured callback URL: `<configured callback path>/<callback_id>`
The local callback server already compares the incoming request path
against the path from the redirect URI. By appending the callback id
before both authorization and callback validation, callbacks that arrive
on the old generic path or a mismatched callback-id path are rejected.
The callback id is bound to the MCP endpoint URL, including path and
query, so path-based multi-tenant MCP deployments on the same origin do
not share a callback path. URL fragments are ignored because they are
not sent to the server.
The change lives in `codex-rmcp-client`, so it covers both the normal
desktop MCP OAuth login path and silent/plugin-triggered MCP OAuth login
paths that use the same `perform_oauth_login_*` helpers.
## Scope and non-goals
- This does not change the app-server protocol or desktop webview
request shape.
- This does not implement RFC 9207 `iss` validation; issuer validation
is still useful when providers return `iss`.
- This does not make arbitrary untrusted MCP servers safe to use. It
specifically adds callback URL binding for the local MCP OAuth flow.
## Validation
- `cargo fmt --all`
- `cargo test -p codex-rmcp-client perform_oauth_login`
## Why
`TurnContext::cwd` is deprecated in favor of resolving paths from the
selected turn environment cwd. A few filesystem-oriented paths were
still constructing sandbox context from the legacy cwd and then mutating
it afterward, or resolving local file paths through the deprecated
helper.
## What changed
- Make `TurnContext::file_system_sandbox_context` take the trusted cwd
explicitly.
- Pass the selected turn environment cwd directly from `apply_patch` and
`view_image` call sites.
- Restrict `spawn_agents_on_csv` to exactly one local environment and
resolve input/output CSV paths from that local environment cwd.
- Remove a redundant test setup assignment that only synchronized
deprecated `TurnContext::cwd` with a replaced config.
## Validation
- `cargo test -p codex-core view_image`
- `cargo test -p codex-core
maybe_persist_mcp_tool_approval_writes_project_config_for_project_server`
- `cargo test -p codex-core parse_csv_supports_quotes_and_commas`
- `git diff --check`
## Why
Remote control starts by letting `codex-backend` initialize against the
app-server as an infrastructure health/proxy client before the real
remote client connects. App-server initialization also sets the
process-wide `originator` from `client_info.name`, so `codex-backend`
could become the sticky originator for later model/API requests even
after the real client initialized.
## What changed
- Treat `codex-backend` as a non-originating initialize client,
alongside the existing `codex_app_server_daemon` probe client.
- Preserve normal per-connection initialize behavior, including session
metadata and initialize analytics.
- Add regression coverage that verifies `codex-backend` initialize does
not replace the default originator.
## Testing
- `cargo test -p codex-app-server --test all
initialize_codex_backend_does_not_override_originator`
## Summary
It appears this config flag has been broken/a noop for quite some time:
since https://github.com/openai/codex/pull/8850. Let's simplify and get
rid of this.
## Testing
- [x] Updated unit tests
## Why
Elevated Windows sandbox setup currently assumes that the firewall rules
it writes will take effect. On managed Windows hosts, local firewall
policy changes can be ignored or only partially apply across the active
profiles, which means setup can appear to succeed without providing the
expected network isolation.
## What changed
- Query `INetFwPolicy2::LocalPolicyModifyState` before configuring the
elevated sandbox firewall rules.
- Fail setup when Windows reports that local firewall policy edits are
ineffective or only apply to some current profiles.
- Surface that condition with a dedicated
`helper_firewall_policy_ineffective` setup error code so support and
IT-facing diagnostics can distinguish it from COM access failures.
- Add focused coverage for effective policy, group-policy override, and
partial-profile coverage cases.
## Testing
- `cargo test -p codex-windows-sandbox --bin
codex-windows-sandbox-setup`
## Why
`chatwidget.rs` is still carrying too many unrelated responsibilities in
one file. #22269 started a five-phase cleanup to move coherent behavior
domains into focused modules while keeping `chatwidget.rs` as the
composition layer. #22407 completed phase 2 by extracting input and
submission flow, and #22433 completed phase 3 by extracting protocol,
replay, streaming, and tool lifecycle handling.
This PR is phase 4. It keeps moving high-churn UI coordination out of
the central widget by extracting settings, popups, and status surfaces
without changing the visible behavior those flows already provide. This
is once again a mechanical movement of existing functions. No functional
changes.
## What Changed
- Added focused modules for runtime settings/model coordination,
model/reasoning/collaboration popups,
settings/personality/theme/audio/experimental popups, permission
prompts, status setup/output controls, and Windows sandbox prompt flows.
- Moved the remaining rate-limit nudge/status helpers and connectors
popup/loading/update helpers into their existing focused modules.
- Preserved the existing picker flows, approval behavior, status/title
setup previews, rate-limit notices, and connectors/app list behavior
while shrinking `chatwidget.rs` back toward orchestration.
- Left `codex-rs/tui/src/chatwidget.rs` as the registration and
composition surface for these extracted behaviors.
## Cleanup Phases
The five-phase cleanup plan from #22269 is:
1. Phase 1: mechanical helper and state moves. Completed in #22269.
2. Phase 2: extract input and submission flow, including queued user
messages, shell prompt submission, pending steer restoration, and thread
input snapshot/restore behavior. Completed in #22407.
3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
handling, while preserving active-cell grouping, transcript
invalidation, interrupt deferral, and final-message separator behavior.
Completed in #22433.
4. Phase 4: extract settings, popups, and status surfaces, including
model/reasoning/collaboration/personality popups, permission prompts,
rate-limit UI, and connectors helpers. This PR.
5. Phase 5: clean up the remaining constructor and orchestration code
once the larger behavior domains have moved out, leaving `chatwidget.rs`
as the composition layer.
## Verification
- `cargo check -p codex-tui`
- `cargo test -p codex-tui chatwidget::tests::permissions`
- `cargo test -p codex-tui chatwidget::tests::status_surface_previews`
- `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
- `cargo test -p codex-tui chatwidget::tests::status_and_layout`
`cargo test -p codex-tui` also compiles and begins running, but aborts
in the unchanged app-side test
`app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
with a reproducible stack overflow.
## Why
`TurnContext::cwd` and `TurnContext::resolve_path` are being phased out
in favor of using the selected turn environment cwd directly.
Deprecating both APIs makes any new direct dependency visible while
preserving the existing migration path for current callers.
## What Changed
- Marked `TurnContext::cwd` and `TurnContext::resolve_path` as
deprecated with guidance to use the selected turn environment cwd
instead.
- Added exact `#[allow(deprecated)]` suppressions at each existing
direct usage site, including tests, rather than adding crate-wide
suppression.
- Kept the change behavior-preserving: current cwd reads, writes, and
path resolution continue to use the same values.
## Verification
- `just fmt`
- `cargo check -p codex-core`
- `cargo check -p codex-core --tests`
- `git diff --check`