Return successful shortcut edits and removals to the main `keymap`
picker with the edited action selected. This keeps the list flow stable
after save instead of reopening the focused binding menu.
Keep shortcut rows focused on section, status, action, and binding. Move selected action descriptions into the footer note and add a compact legend for custom and unbound shortcut markers.
Show custom and unbound keymap states with compact indicators in the shortcut picker. This removes the default-state prefix so ordinary shortcuts scan by section while changes and missing bindings remain visible.
Add a curated Common tab to `/keymap` for the shortcuts most
likely to be rebound, while keeping All as the initial view.
Demote onboarding from the top-level picker categories without
removing its keymap actions from All or search.
Adds explicit replace-all, replace-one, and add-alternate flows to the `/keymap` action menu so multi-binding actions can be edited without losing the full shortcut set.
Persists one binding as a scalar and multiple bindings as an array under `tui.keymap`, matching the existing config shape while preserving default-derived alternates.
Move `/keymap` to a tabbed, single-line picker inspired by the
`/plugins` command so shortcut browsing is denser and less repetitive.
The picker now groups shortcuts by source and context, with compact row
status markers for default, custom, and unbound actions.
Move selected-action details into the edit menu header and remove the
old side panel so the picker stays focused on scanning and searching.
Allow an empty Vim normal-mode composer to treat / as the command
shortcut by seeding the slash prefix and returning to insert mode.
This keeps popup behavior consistent with the footer hint while
preserving normal-mode editing for non-empty drafts.
Keep the keymap action menu on the bottom-pane stack while capturing or clearing a shortcut so users return to the previous menu instead of the composer.
Refresh the active action menu after keymap updates so the displayed binding and clear state match the saved config.
Remove the duplicate slash-command dispatcher left by the rebase and wire the vim/keymap commands into the extracted dispatcher.
Update affected keymap test callsites and footer snapshot so the rebased TUI builds against the current list keymap API.
Use a non-copy chord in the queue shadowing regression test so it still reaches the intended composer conflict after `global.copy` claimed `ctrl-o`.
Accept the keymap picker snapshots now that Copy is part of the configurable global keymap actions.
Add missing argument comments around positional boolean and numeric
literals that the TUI keymap work now reaches.
This keeps callsites self-documenting and satisfies the
argument-comment lint in CI.
Set the manual `ChatWidget` test helper to use the default copy
shortcut binding so helper-built widgets match normal construction.
This fixes the CI compile failure from the new remappable copy field.
Add `global.copy` to the TUI keymap schema, runtime keymap,
and interactive remap action list so the Copy shortcut can be
remapped or unbound like other global shortcuts.
Move the existing `ctrl-o` default into a new `v3` preset while
preserving older presets, and update docs plus coverage for the
remapped copy behavior.
Adds a `/keymap` flow for discovering actions, replacing root-level shortcut bindings, and removing custom bindings when users want to return to defaults.
The picker uses responsive layouts so wide terminals show richer action details while narrow terminals keep the guidance compact.
Regenerate the config schema after adding keymap rustdoc so the
fixture matches the generated `config.toml` schema.
Add the required argument comments for opaque literals in the vim and
cursor tests so the branch passes the argument-comment lint.
Let Vim Insert handle plain `Esc` before the chat edit-previous and backtrack shortcut layers so an empty composer can still return to Normal mode.
Keep existing empty-composer backtrack behavior after Vim has reached Normal mode, and cover both composer-local and app-level routing.
Thread cursor style through the custom terminal and render tree so the chat composer can request an insert-like cursor while Vim mode is in Insert.
Restore the terminal default cursor style on Normal/non-Vim frames and TUI teardown, and cover the escape sequences plus composer mode transitions.
Preserve mainline submit/queue semantics, update approval tests for
`SubmitThreadOp`, and keep permission-deny shortcuts from shadowing
`Esc` cancellation.
Add a `v2` keymap preset so `latest` can restore `alt-d`
`delete_forward_word` without mutating frozen `v1` defaults, and refresh
the generated config schema and docs.
Clarify that shifted letter bindings are matched compatibly when
terminals emit uppercase letters without an explicit SHIFT modifier.
Update config guidance, the default keymap template, and the action
matrix with examples for shift-i, shift-a, and shift-o mapping to I, A,
and O.
Make shifted letter bindings robust when terminals report uppercase
letters without explicit SHIFT modifiers.
This updates keybinding matching so bindings like `shift-i`, `shift-a`,
and `shift-o` also match `I`, `A`, and `O` event forms, and adds
regression tests covering Vim normal-mode actions for line-start insert,
line-end append, and open-line-above with shift-only bindings.
Update `tui/src/bottom_pane/textarea.rs` so `Esc` in Vim insert mode
moves the cursor to the previous atomic boundary before switching to
normal mode. This matches Vim behavior at end-of-line and avoids
leaving the cursor on a virtual trailing cell.
Add regression coverage in `tui/src/bottom_pane/textarea.rs` and
`tui/src/bottom_pane/chat_composer.rs`, plus a
`vim_escape_cursor_position` snapshot to lock rendered cursor
placement after `Esc`.
Expose Vim bindings through the TUI keymap config and add a startup
toggle so Vim mode can be enabled by default.
Wire runtime keymap handling for Vim actions, add
`tui.vim_mode_default` to config parsing/runtime config, apply the
default in ChatWidget startup paths, and update docs/default
keymap/schema accordingly.
Actions placed directly under [tui.keymap] instead of a context
sub-table (e.g. [tui.keymap.global]) were silently ignored because
deny_unknown_fields was only on the schemars attribute, not serde.
Add a dedicated footer line pointing to `[tui.keymap]` in
`~/.codex/config.toml` so users can find where to rebind shortcuts.
Refresh tooltips and snapshots to mention the config entry and
the keymap template URL.
Introduce the TUI runtime keymap resolver and keybinding matching helpers
with a dedicated unit-test suite. This commit is additive only: it adds
resolution logic, conflict validation, parser coverage, and documented macros
without wiring input handlers to the new runtime map yet.
Introduce keymap configuration types and schema support in core without
wiring runtime key handling yet. This keeps behavior unchanged while
adding the configuration surface needed by later commits.
Publish keymap system documentation first so the implementation stack can
be reviewed against explicit behavior and invariants.
This commit adds the keymap system guide, action matrix, default keymap
template, and config/example documentation updates, plus a rollout plan
used to stage the additive refactor and validation work.
## Why
`multi_agent_v2` uses the v2 agent lifecycle, so accepting the legacy
`agents.max_threads` limit alongside it creates conflicting
configuration semantics. Config load should fail early with a clear
error instead of allowing both knobs to be set.
## What Changed
- During config load, detect when the effective `multi_agent_v2` feature
is enabled and `agents.max_threads` is explicitly set.
- Return an `InvalidInput` error: `agents.max_threads cannot be set when
multi_agent_v2 is enabled`.
## Verification
- `cargo test -p codex-core multi_agent_v2_rejects_agents_max_threads`
passed locally with a temporary focused test for this behavior.
- `cargo test -p codex-core` was also run; the new focused path passed,
but the crate suite has unrelated pre-existing failures in managed
config/proxy/request-permissions tests.
## Why
This keeps the partial Guardian subagent -> Auto-review rename
forward-compatible across mixed Codex installations. Newer binaries need
to understand the new `auto_review` spelling, but they cannot write it
to shared `~/.codex/config.toml` yet because older CLI/app-server
bundles only know `user` and `guardian_subagent` and can fail during
config load before recovering.
The Python SDK had the opposite compatibility gap: app-server responses
can contain `approvalsReviewer: "auto_review"`, but the checked-in
generated SDK enum did not accept that value.
## What Changed
- Keep `ApprovalsReviewer::AutoReview` readable from both
`guardian_subagent` and `auto_review`, while serializing it as
`guardian_subagent` in both protocol crates.
- Update TUI Auto-review persistence tests so enabling Auto-review
writes `approvals_reviewer = "guardian_subagent"` while UI copy still
says Auto-review.
- Map managed/cloud `feature_requirements.auto_review` to the existing
`Feature::GuardianApproval` gate without adding a broad local
`[features].auto_review` key or changing config writes.
- Add `auto_review` to the Python SDK `ApprovalsReviewer` enum and cover
`ThreadResumeResponse` validation.
## Testing
- `cargo test -p codex-protocol approvals_reviewer`
- `cargo test -p codex-app-server-protocol approvals_reviewer`
- `cargo test -p codex-tui
update_feature_flags_enabling_guardian_selects_auto_review`
- `cargo test -p codex-tui
update_feature_flags_enabling_guardian_in_profile_sets_profile_auto_review_policy`
- `cargo test -p codex-core
feature_requirements_auto_review_disables_guardian_approval`
- `pytest
sdk/python/tests/test_client_rpc_methods.py::test_thread_resume_response_accepts_auto_review_reviewer`
- `git diff --check`
## Summary
Lifecycle hooks currently treat `PreToolUse`, `PostToolUse`, and
`PermissionRequest` as Bash-only flows
- hook schema constrains `tool_name` to `Bash`
- hook input assumes a command-shaped `tool_input`
- core hook dispatch path passes only shell command strings
That means hooks cannot target MCP tools even though MCP tool names are
model-visible and stable
This change generalizes those hook paths so they can match and receive
payloads for MCP tools while preserving the existing Bash behavior.
## Reviewer Notes
I think these are the key files
- `codex-rs/core/src/tools/handlers/mcp.rs`
- `codex-rs/core/src/mcp_tool_call.rs`
Otherwise the changes across apply_patch, shell, and unified_exec are
mainly to rewire everything to be `tool_input` based instead of just
`command` so that it'll make sense for MCP tools.
## Changes
- Allow `PreToolUse`, `PostToolUse`, and `PermissionRequest` hook inputs
to carry arbitrary `tool_name` and `tool_input` values instead of
hard-coding `Bash` and command-only payloads.
- Add MCP hook payload support through `McpHandler`, using the
model-visible tool name from `ToolInvocation` and the raw MCP arguments
as `tool_input`.
- Include MCP tool responses in `PostToolUse` by serializing
`McpToolOutput` into the hook response payload.
- Run `PermissionRequest` hooks for MCP approval requests after
remembered approval checks and before falling back to user-facing MCP
elicitation.
- Preserve exact matching for literal hook matchers like `Bash` and
`mcp__memory__create_entities`, while keeping regex matcher support for
patterns like `mcp__memory__.*` and `mcp__.*__write.*`.
---------
Co-authored-by: Andrei Eternal <eternal@openai.com>
Co-authored-by: Codex <noreply@openai.com>
## Why
`item/permissions/requestApproval` sends a requested permission profile
to app-server clients. The core profile already stores filesystem
permissions as `entries`, but the v2 compatibility conversion used the
legacy `read`/`write` projection whenever possible and left `entries`
unset.
That made the request ambiguous for clients that consume the canonical
v2 shape: `permissions.fileSystem.entries` was missing even though
filesystem access was being requested. A client that rendered or echoed
grants from `entries` could treat the request as having no filesystem
permission entries, then return an empty or incomplete grant. The
app-server intersects responses with the original request, so omitted
filesystem permissions are denied.
## What Changed
- Populate `AdditionalFileSystemPermissions.entries` when converting
legacy read/write roots for request permission payloads, while
preserving `read` and `write` for compatibility.
- Mark `read` and `write` as transitional schema fields in the generated
app-server schema.
- Add regression coverage for the v2 conversion, the app-server
`item/permissions/requestApproval` round trip, and TUI app-server
approval conversion expectations.
- Refresh generated JSON and TypeScript schema fixtures.
## Verification
- `just fmt`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server request_permissions_round_trip`
- `cargo test -p codex-tui
converts_request_permissions_into_granted_permissions`
- `cargo test -p codex-tui
resolves_permissions_and_user_input_through_app_server_request_id`
## Why
When the TUI upgrade flow moves a user to a newer model, the accepted
migration should also persist the target model's default reasoning
effort. That keeps the upgraded model and reasoning setting aligned
instead of carrying forward a stale previously saved effort from the old
model.
## What changed
- The accepted model migration path now updates in-memory config, TUI
state, and persisted model selection with the target preset's
`default_reasoning_effort`.
- The upgrade destructuring keeps `reasoning_effort_mapping` explicitly
unused because mappings are no longer consulted on accepted migrations.
- Added a catalog test that starts with a pre-existing saved reasoning
effort and verifies the accepted upgrade overwrites it with the target
model default and emits the expected persistence events.
- Rebasing onto current `main` also updates a TUI thread-session test
helper for the latest `permission_profile` field and
`ApprovalsReviewer::AutoReview` rename so CI compiles on the new base.
## Verification
- `cargo test -p codex-tui model_catalog`
- `cargo test -p codex-tui
permission_settings_sync_updates_active_snapshot_without_rewriting_side_thread`
## Why
The current cloud-requirements failures say `workspace-managed config`,
which is ambiguous and can read like it refers to local managed config
such as `managed_config.toml`.
This code path only applies to cloud requirements, so the user-facing
message should name that source directly.
## What changed
- Updated the load failure in
[`codex-rs/cloud-requirements/src/lib.rs`](46e704d1f9/codex-rs/cloud-requirements/src/lib.rs)
to say `failed to load cloud requirements (workspace-managed policies)`.
- Updated the parse failure in the same file to use the same `cloud
requirements (workspace-managed policies)` terminology.
- Kept `workspace-managed` hyphenated because it is used as a compound
modifier.
- Updated the matching assertion in
[`codex-rs/app-server/src/codex_message_processor.rs`](46e704d1f9/codex-rs/app-server/src/codex_message_processor.rs).
- Reused `CLOUD_REQUIREMENTS_LOAD_FAILED_MESSAGE` in the
`codex-cloud-requirements` test where the test is asserting that
crate-local contract directly.
## Testing
`cargo test -p codex-cloud-requirements`
Addresses #18854
## Why
The `/permissions` selector updates the active TUI session state, but
the cached session snapshot used when replaying a thread could still
contain the old approval or sandbox settings. After opening and leaving
`/side`, the main thread replay could restore those stale settings into
the `ChatWidget`, so the UI and the next submitted turn could fall back
to the old permission mode.
## What
- Sync the active thread's cached `ThreadSessionState` whenever approval
policy, sandbox policy, or approval reviewer changes.
## Verification
Confirmed bug prior to fix and correct behavior after fix.
# Why
Hooks are ready to graduate to GA in the next release!
# What
- Moves `Feature::CodexHooks` into the stable feature group.
- Marks the `codex_hooks` feature spec as `Stage::Stable` and
default-enabled.
## Why
`command/exec` is another app-server entry point that can run under
caller-provided permissions. It needs to accept `PermissionProfile`
directly so command execution is not left behind on `SandboxPolicy`
while thread APIs move forward.
Command-level profiles also need to preserve the semantics clients
expect from profile-relative paths. `:cwd` and cwd-relative deny globs
should be anchored to the resolved command cwd for a command-specific
profile, while configured deny-read restrictions such as `**/*.env =
none` still need to be enforced because they can come from config or
requirements rather than the command override itself.
## What Changed
This adds `permissionProfile` to `CommandExecParams`, rejects requests
that combine it with `sandboxPolicy`, and converts accepted profiles
into the runtime filesystem/network permissions used for command
execution.
When a command supplies a profile, the app-server resolves that profile
against the command cwd instead of the thread/server cwd. It also
preserves configured deny-read entries and `globScanMaxDepth` on the
effective filesystem policy so one-off command overrides cannot drop
those read protections. The PR also updates app-server docs/schema
fixtures and adds command-exec coverage for accepted, rejected,
cwd-scoped, and deny-read-preserving profile paths.
## Verification
- `cargo test -p codex-app-server
command_exec_permission_profile_cwd_uses_command_cwd`
- `cargo test -p codex-app-server
command_profile_preserves_configured_deny_read_restrictions`
- `cargo test -p codex-app-server
command_exec_accepts_permission_profile`
- `cargo test -p codex-app-server
command_exec_rejects_sandbox_policy_with_permission_profile`
- `just fix -p codex-app-server`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18283).
* #18288
* #18287
* #18286
* #18285
* #18284
* __->__ #18283
## Why
Enterprise and business-like ChatGPT plans should get Codex's Fast
service tier by default when the user or caller has not made an explicit
service-tier choice. At the same time, callers need a durable way to
choose standard routing without adding a new persisted `standard`
service tier value. This keeps existing config compatibility while
letting core own the managed default policy.
## What changed
- Resolve the effective service tier in core at session creation:
explicit `fast` or `flex` wins, explicit null/clear or
`[notice].fast_default_opt_out = true` resolves to standard routing, and
otherwise eligible ChatGPT plans resolve to Fast when FastMode is
enabled.
- Add `[notice].fast_default_opt_out` as the persisted opt-out marker
for managed Fast defaults.
- Treat app-server/TUI `service_tier: null` as an explicit
standard/clear choice by preserving that intent through config loading.
- Update TUI rendering to use core's effective service tier for startup
and status surfaces while still keeping `config.service_tier` as the
explicit configured choice.
- Update `/fast off` to clear `service_tier`, persist the opt-out
marker, and send explicit standard for subsequent turns.
## Verification
- Added unit coverage for config override/notice handling, service-tier
resolution, runtime null clearing, and `/fast off` turn propagation.
- `cargo build -p codex-cli`
Full test suite was not run locally per author request.
## Why
Clients that observe `SessionConfigured` need the same canonical
permission view that app-server thread responses provide. Reporting the
profile in protocol events lets clients keep their local state
synchronized without reinterpreting legacy sandbox fields.
## What changed
This adds `permission_profile` to `SessionConfigured` and propagates it
through core, exec JSON output, MCP server messages, and TUI
history/widget handling.
## Verification
- `cargo test -p codex-tui permissions -- --nocapture`
- `cargo test -p codex-core --test all permissions_messages --
--nocapture`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18282).
* #18288
* #18287
* #18286
* #18285
* #18284
* #18283
* __->__ #18282
## Summary
Support the existing hooks schema in inline TOML so hooks can be
configured from both `config.toml` and enterprise-managed
`requirements.toml` without requiring a separate `hooks.json` payload.
This gives enterprise admins a way to ship managed hook policy through
the existing requirements channel while still leaving script delivery to
MDM or other device-management tooling, and it keeps `hooks.json`
working unchanged for existing users.
This also lays the groundwork for follow-on managed filtering work such
as #15937, while continuing to respect project trust gating from #14718.
It does **not** implement `allow_managed_hooks_only` itself.
NOTE: yes, it's a bit unfortunate that the toml isn't formatted as
closely as normal to our default styling. This is because we're trying
to stay compatible with the spec for plugins/hooks that we'll need to
support & the main usecase here is embedding into requirements.toml
## What changed
- moved the shared hook serde model out of `codex-rs/hooks` into
`codex-rs/config` so the same schema can power `hooks.json`, inline
`config.toml` hooks, and managed `requirements.toml` hooks
- added `hooks` support to both `ConfigToml` and
`ConfigRequirementsToml`, including requirements-side `managed_dir` /
`windows_managed_dir`
- treated requirements-managed hooks as one constrained value via
`Constrained`, so managed hook policy is merged atomically and cannot
drift across requirement sources
- updated hook discovery to load requirements-managed hooks first, then
per-layer `hooks.json`, then per-layer inline TOML hooks, with a warning
when a single layer defines both representations
- threaded managed hook metadata through discovered handlers and exposed
requirements hooks in app-server responses, generated schemas, and
`/debug-config`
- added hook/config coverage in `codex-rs/config`, `codex-rs/hooks`,
`codex-rs/core/src/config_loader/tests.rs`, and
`codex-rs/core/tests/suite/hooks.rs`
## Testing
- `cargo test -p codex-config`
- `cargo test -p codex-hooks`
- `cargo test -p codex-app-server config_api`
## Documentation
Companion updates are needed in the developers website repo for:
- the hooks guide
- the config reference, sample, basic, and advanced pages
- the enterprise managed configuration guide
---------
Co-authored-by: Michael Bolin <mbolin@openai.com>