Commit Graph
1960 Commits
Author SHA1 Message Date
Eric TrautandGitHub 22e45014a2 TUI config cleanup: plugin mentions (#24266)
## Summary

TUI plugin mention refresh still joined app-server plugin inventory with
client-local plugin config, which can diverge once plugin state is owned
by the app server.

This changes the TUI to mirror the GUI client: `plugin/list` is the
autocomplete source, and mention candidates are plugin-level entries
filtered to installed, enabled, and not disabled by admin. The TUI no
longer reads local plugin config or calls `plugin/read` while refreshing
plugin mention candidates.

## API shape and limitations

The current app-server API does not expose effective per-session plugin
capability summaries for mention autocomplete. As in the GUI,
autocomplete now trusts `plugin/list` metadata rather than proving which
plugin capabilities are loaded in the active session.

That avoids stale client-local reads and the cwd/remote detail gaps in
`plugin/read`, but intentionally accepts the same list-level tradeoff as
the app: if `plugin/list` reports a remote plugin before its local
bundle is materialized, the plugin can still appear as a mention
candidate.
2026-05-26 14:34:02 -07:00
pakrym-oaiandGitHub 768848ab6f Add experimental turn additional context (#24154)
## Summary

Adds experimental `additionalContext` support to `turn/start` and
`turn/steer` so clients can provide ephemeral external context, such as
browser or automation state, without turning that plumbing into a
visible user prompt or triggering user-prompt lifecycle behavior.

## API Shape

The parameter shape is:

```ts
additionalContext?: Record<string, {
  value: string
  kind: "untrusted" | "application"
}> | null
```

Example:

```json
{
  "additionalContext": {
    "browser_info": {
      "value": "Active tab is CI failures.",
      "kind": "untrusted"
    },
    "automation_info": {
      "value": "CI rerun is in progress.",
      "kind": "application"
    }
  }
}
```

The keys are opaque and caller-defined.

## Context Injection

When provided, accepted entries are inserted into model context as
hidden contextual message items, not as visible thread user-message
items.

`kind: "untrusted"` entries are inserted with role `user`:

```text
<external_${key}>${value}</external_${key}>
```

`kind: "application"` entries are inserted with role `developer`:

```text
<${key}>${value}</${key}>
```

Values are not escaped. Each value is truncated to 1k approximate tokens
before wrapping.

For `turn/start`, accepted additional context is inserted before normal
user input. For `turn/steer`, additional context is merged only when the
steer includes non-empty user input; context-only steers still reject as
empty input.

## Dedupe Strategy

`AdditionalContextStore` lives on session state and stores the latest
complete additional-context map.

Each `turn/start` or non-empty `turn/steer` treats its
`additionalContext` as the current complete set of values. Entries are
injected only when the key is new or the exact entry for that key
changed, including `value` or `kind`. After merging, the store is
replaced with the provided map, so omitted keys are removed from the
retained set and can be injected again later if reintroduced.

Omitting `additionalContext`, passing `null`, or passing an empty object
resets the store to empty and injects nothing.

## What Changed

- Threads experimental v2 `additionalContext` through app-server into
core turn start and steer handling.
- Adds separate contextual fragment types for untrusted user-role
context and application developer-role context.
- Uses pending response input items so additional context can be
combined with normal user input without treating it as prompt text.
- Adds integration coverage for start/steer flow, role routing,
dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
behavior, empty context-only steer rejection, external-fragment marker
matching, and truncation.
2026-05-26 13:02:34 -07:00
canvrno-oaiandGitHub cd934c8bcb tui: keep inaccessible apps out of mentions (#24625)
## Summary

Fix the TUI `$` app mention paths so App Directory rows that are not
accessible are not treated as usable apps.

This includes the core preservation fix from #24104, but expands it to
the other app mention paths:

- preserve app-server `is_accessible` flags when partial
`app/list/updated` snapshots reach the TUI
- require apps to be both accessible and enabled when resolving exact
`$slug` mentions
- require restored/stale `app://...` bindings to point at accessible,
enabled apps before emitting structured app mentions
- remove the now-unused `codex-chatgpt` dependency from `codex-tui`,
which addresses the `cargo shear` failure seen on #24104

## Root Cause

The app server already sends merged app snapshots with accessibility
computed. The TUI handled app-server app list updates as partial app
loads and re-ran the old accessible-app merge path. That path treated
every notification row as accessible, so App Directory entries with
`isAccessible=false` could appear in `$` suggestions.

Regression source: #22914 routed app-list updates through the app server
while reusing the old TUI partial-load handling. Related precursor:
#14717 introduced the partial-load path, but #22914 made it user-visible
for app-server updates.

## Issues

Fixes #24145
Fixes #24205
Fixes #24319

## Validation

- `just fmt`
- `git diff --check`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just argument-comment-lint -p codex-tui`
- `just test -p codex-tui
chatwidget::tests::popups_and_settings::apps_notification_update_excludes_inaccessible_apps_from_mentions
chatwidget::tests::composer_submission::submit_user_message_ignores_inaccessible_app_mentions_from_bindings
chatwidget::skills::tests::find_app_mentions_requires_accessible_enabled_apps_for_bound_paths
chatwidget::skills::tests::find_app_mentions_requires_accessible_enabled_apps_for_slugs`
2026-05-26 12:09:07 -07:00
Felipe CouryandGitHub 833c19ed53 fix(tui): keep raw output above composer in zellij (#24593)
## Why

Raw output mode intentionally sends logical source lines to the terminal
without Codex-inserted wrapping so copied content retains its original
line structure. In Zellij, soft-wrapped continuation rows from those raw
lines are not confined by the inline history scroll region. When raw
mode replays a long transcript, continuation rows can occupy the
composer viewport and are overwritten on the following draw, leaving the
transcript visibly truncated underneath the composer.

This is specific to the combination of Zellij and raw terminal-wrapped
history. Rich output and non-Zellij terminals should continue using the
existing insertion behavior.

Related context: #20819 introduced raw output mode, and #22214 removed
the broad Zellij insertion workaround after the standard rich-output
path no longer required it.

| Before | After |
|---|---|
| <img width="1728" height="916" alt="image"
src="https://github.com/user-attachments/assets/f85398a5-e930-46d9-bcfd-106a24c41466"
/> | <img width="1723" height="912" alt="image"
src="https://github.com/user-attachments/assets/5c62e16a-a6e5-4842-bcb2-eab163cda04c"
/> |

## What Changed

- Cache Zellij detection in `Tui` and select a dedicated insertion mode
only for `HistoryLineWrapPolicy::Terminal` batches in Zellij.
- For that guarded path, clear the existing viewport, append raw source
lines through the terminal so its soft wrapping remains
selection-friendly, and reserve empty viewport rows before redrawing the
composer.
- Add snapshot regressions for both an incremental soft-wrapped raw
insert and an overflowing raw transcript replay that starts at the top
of the cleared terminal.

## How to Test

1. Start Codex inside Zellij with raw output enabled or toggle raw
output after a multiline response is in history.
2. Produce or replay output containing long logical lines, such as a
fenced shell command with several wrapped lines.
3. Confirm the wrapped history remains visible above the composer and
the composer no longer overwrites the end of the response.
4. Toggle back to rich output or run outside Zellij and confirm standard
history rendering still behaves normally.

Targeted tests run:

- `just test -p codex-tui vt100_zellij_raw -- --nocapture`

Additional validation notes:

- `just test -p codex-tui` was attempted; the two new Zellij raw
insertion tests passed, while two existing
`app::tests::update_feature_flags_disabling_guardian_*` tests failed
outside this history insertion path.
- `just argument-comment-lint` was attempted but local Bazel analysis
fails before reaching the changed source because the LLVM `compiler-rt`
package is missing `include/sanitizer/*.h`. Modified literal callsites
were inspected manually.
2026-05-26 16:08:45 -03:00
Felipe CouryandGitHub 8a4a537e44 fix(tui): avoid modifyOtherKeys for unknown tmux formats (#24371)
## Why

Codex 0.131 started enabling tmux `modifyOtherKeys` mode 2 when the
active tmux session reported `extended-keys-format csi-u`, and also when
that format could not be queried. The fallback was meant to help
compatible tmux panes enter extended-key mode, but it breaks iTerm2
control-mode sessions on older tmux.

Issue #23711 reproduces with:

```bash
ssh -t ubuntu@192.168.68.149 'tmux -CC new -A -s main'
```

On tmux 3.2a, `extended-keys-format` is not available. With mode 2
enabled, `Ctrl-C` is delivered as `^[[27;5;99~` instead of the normal
interrupt/control key path, so Codex does not handle it. Running with
`CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1` restores `Ctrl-C`, which
points at keyboard mode setup rather than chat input routing.

## What Changed

- Only request `modifyOtherKeys` mode 2 when tmux explicitly reports
`extended-keys-format csi-u`.
- Treat an unknown or unavailable tmux extended-key format as
unsupported for this mode.
- Update the keyboard mode unit coverage so `None` no longer opts into
`modifyOtherKeys`.

This preserves the explicit modern tmux `csi-u` path from #21943 while
avoiding the unsafe fallback on older or unqueryable tmux setups.

## How to Test

Regression path from #23711:

1. Start iTerm2 tmux integration against an older tmux host:
   ```bash
   ssh -t ubuntu@192.168.68.149 'tmux -CC new -A -s main'
   ```
2. Start patched Codex.
3. Run `/keymap debug`, press a regular key, then press `Ctrl-C`.
4. Confirm `Ctrl-C` closes the inspector and Codex remains responsive
without `CODEX_TUI_DISABLE_KEYBOARD_ENHANCEMENT=1`.
5. Confirm `Shift+Enter` still inserts a newline in the same session.

Modern tmux compatibility path:

1. Start an ordinary tmux 3.6a server with explicit `csi-u`:
   ```bash
   tmux -L codex-csiu -f /dev/null new-session -d -s repro
   tmux -L codex-csiu set-option -g extended-keys on
   tmux -L codex-csiu set-option -g extended-keys-format csi-u
   tmux -L codex-csiu attach -t repro
   ```
2. Start patched Codex.
3. From another terminal, confirm the Codex pane reports `mode=Ext 2`:
   ```bash
tmux -L codex-csiu list-panes -a -F '#{pane_id} mode=#{pane_key_mode}
cmd=#{pane_current_command}'
   ```
4. Type `one`, press `Shift+Enter`, type `two`, and confirm the composer
shows two lines without submitting.
5. Press `Ctrl-C` and confirm Codex handles it normally.

Targeted tests:

- `./tools/argument-comment-lint/run.py -p codex-tui -- --lib`
- `just test -p codex-tui` runs the new keyboard mode test successfully;
the full run currently reports two unrelated guardian feature-flag test
failures:
-
`app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`
-
`app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`

No documentation update is needed.
2026-05-26 14:54:38 -03:00
canvrno-oaiandGitHub db9cb04fb6 Move slash input logic out of chat composer (#23964)
Recent composer cleanups split state ownership out of `ChatComposer`,
but slash-command handling still mixed parsing, popup coordination,
completion, submission validation, queue behavior, and argument element
rebasing into the main composer file. Pending changes to slash command
parsing and selection inspired this code move to prevent
`chat_composer.rs` bloat.

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

## What changed

- Move slash-command parsing and lookup helpers into
`bottom_pane/chat_composer/slash_input.rs`.
- Move slash popup key handling, command-name completion, and popup
construction into the slash input helper module.
- Centralize bare-command, inline-args, submission-validation, and
queued-input action selection behind slash-specific helpers.
- Move command argument text-element rebasing into the slash input
module so inline command submission keeps the same element behavior with
less composer-local logic.

## Verification

- `just fmt`
- `just test -p codex-tui`
- `cargo insta pending-snapshots -p codex-tui`
2026-05-26 10:29:15 -07:00
viyatb-oaiandGitHub f6fd753039 tui: add named permission profile picker (#21559)
## Why

Users who opt into named permission profiles through
`default_permissions` or `[permissions.*]` should stay in named-profile
semantics when they open `/permissions`. The legacy picker rewrites
those users into anonymous preset state, which loses the active profile
identity and hides custom configured profiles.

## What changed

- Switch `/permissions` to a profile-aware picker when profile mode is
active.
- Show friendly built-in labels instead of raw `:` profile syntax.
- Include configured custom profiles and their descriptions in the
picker.
- Route selections through the split TUI profile-selection flow below
this PR.
- Add TUI snapshots and regression coverage for built-ins, custom
profiles, and conflicting legacy runtime overrides.

## Stack

1. [#22931](https://github.com/openai/codex/pull/22931):
runtime/session/network propagation for active permission profiles.
2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
plumbing and guardrail flow.
3. **This PR**: profile-aware `/permissions` menu and custom profile
display.

## UX impact

In profile mode, `/permissions` shows the same human-facing built-ins
users already know:

```text
Default
Auto-review
Full Access
Read Only
locked-down
web-enabled
```

Selecting `locked-down` keeps `active_permission_profile =
Some("locked-down")`; selecting a built-in keeps the friendly label
while switching to its named built-in profile.

## Screenshots

Live `$test-tui` smoke screenshots uploaded through GitHub attachments:

**Profile mode with built-ins and custom profiles**

<img width="832" alt="Profile mode permissions picker with custom
profiles"
src="https://github.com/user-attachments/assets/58b72431-418c-4839-9e39-575076db4c8f"
/>

**Legacy mode remains anonymous preset picker**

<img width="1232" alt="Legacy permissions picker"
src="https://github.com/user-attachments/assets/95f413ab-4cee-411c-9afb-92580a885c97"
/>

<img width="1296" height="906" alt="image"
src="https://github.com/user-attachments/assets/ea381a78-9904-4aa2-828f-b7f2e43f60f2"
/>

<img width="705" height="207" alt="Screenshot 2026-05-18 at 2 58 00 PM"
src="https://github.com/user-attachments/assets/2fa6dd71-0296-449e-a6de-a72d78a1cb70"
/>

## Validation

- `git diff --cached --check` before commit.
- Full test run skipped at the user request while pushing the split
stack.
2026-05-26 16:39:55 +00:00
Eric TrautandGitHub b84c5898df tui: include exec sessions in resume list (#24503)
## Why

Fixes #24502.

`codex resume --include-non-interactive` should include sessions created
by `codex exec`, but the TUI was sending no `sourceKinds` filter to
`thread/list` for that mode. `thread/list` treats omitted or empty
`sourceKinds` as interactive-only (`cli`, `vscode`), so exec sessions
were still filtered out.

## What Changed

- Added a shared TUI `resume_source_kinds` helper so both resume lookup
paths always pass explicit `sourceKinds` to `thread/list`.
- Kept the default resume behavior scoped to `cli` and `vscode`.
- Made `--include-non-interactive` include `exec` and `appServer`
sessions, while continuing to exclude subagent and unknown sources.

## Verification

Added focused coverage for both affected TUI request builders:

- `latest_session_lookup_params_can_include_non_interactive_sources`
- `remote_thread_list_params_can_include_non_interactive_sources`
2026-05-26 08:27:10 -07:00
Eric TrautandGitHub 0f91e869bd Use thread config for TUI MCP inventory (#24532)
## Summary
`/mcp` in the TUI should reflect the current loaded thread, including
project-local MCP servers from that thread config. Before this change,
`mcpServerStatus/list` only read the latest global MCP config, so the
active chat could miss project-local servers.

This adds optional `threadId` to `mcpServerStatus/list`. When present,
app-server resolves the loaded thread and lists MCP status from the
refreshed effective config for that thread; when omitted, existing
global config behavior stays unchanged.

The TUI now sends the active chat thread id for `/mcp` and `/mcp
verbose`, carries that origin through the async inventory result, and
ignores stale completions if the user has switched threads before the
fetch returns. The app-server schemas were regenerated.

## Follow-up
Once this app-server API change lands, the desktop app should make the
same `threadId` plumbing so its MCP inventory also uses the current
thread config.

Fixes #23874
2026-05-26 07:44:04 -07:00
Felipe CouryandGitHub 599416d733 fix(tui): prevent macos stderr from corrupting composer (#24459)
## Why

Fixes #17139.

On macOS, runtime diagnostics such as `MallocStackLogging` messages can
be written directly to process stderr while the inline TUI owns the
terminal. Those bytes paint into the same viewport as the composer
without passing through the renderer or composer state, making
diagnostic output appear to leak into the input area.

## What Changed

- Add a macOS terminal stderr guard while the inline TUI owns the
viewport.
- Restore stderr when Codex returns terminal ownership for external
interactive programs, suspend/resume, panic handling, and normal
shutdown.
- Add an fd-level regression test that verifies output is suppressed
only while terminal ownership is held and restored at each handoff
boundary.

## How to Test

1. On macOS, launch the interactive TUI and leave the composer visible.
2. Exercise the workflow that triggers an allocator/runtime stderr
diagnostic during an active session, as reported in #17139.
3. Confirm the diagnostic no longer overwrites the active composer
region.
4. Suspend or exit the TUI and confirm subsequent terminal stderr output
remains visible.

The platform diagnostic is environment-dependent, so the deterministic
regression check is the new fd-lifecycle test in
`tui::terminal_stderr::tests::suppresses_stderr_only_while_terminal_is_owned`.

Targeted validation:
- `just argument-comment-lint-from-source -p codex-tui` passed.
- `just test -p codex-tui` exercised and passed the new stderr-guard
regression test. The full invocation currently fails in two unrelated
guardian-policy tests,
`update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
and
`update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`,
which reproduce when rerun in isolation.
2026-05-25 19:53:40 +00:00
Felipe CouryandGitHub 14d80e55cd fix(tui): improve multiline markdown list readability (#24351)
## Why

Numbered Markdown findings become hard to scan when long items visually
run together or when wrapped explanatory paragraphs lose their list
indentation. This is especially visible in review output: the next
number can look attached to the previous finding, and paragraph
continuation rows can jump back toward the left margin instead of
staying grouped beneath their item.

<table><tr><td>
<center>Before</center>
<img width="1718" height="836" alt="CleanShot 2026-05-24 at 14 00 49"
src="https://github.com/user-attachments/assets/f1ee0023-50fa-4f81-a641-ae08b17b99bd"
/>
</td></tr>
<tr><td> 
<center>After</center>
<img width="1714" height="906" alt="image"
src="https://github.com/user-attachments/assets/b123a5e0-a232-47bf-96d5-c935295f7c0a"
/>
</td></tr>
</table>

## What Changed

- Insert a blank separator before a sibling list item when the previous
item occupies more than one rendered line.
- Preserve compact rendering for lists whose sibling items each render
on one line.
- Preserve list-body leading whitespace when transient streamed
assistant rows require another wrapping pass for history display, so
wrapped paragraphs stay aligned beneath their item.
- Share the existing leading-whitespace prefix logic used by history
insertion instead of introducing a second indentation rule.
- Keep streamed Markdown output aligned with completed rendering and add
snapshots for findings-style spacing and streamed paragraph indentation.

## How to Test

1. Start Codex from this branch and open the recorded repro session
`019e563f-7d58-7ff2-8ec7-828f20fa61ca`.
2. Inspect the numbered `Findings` list whose items contain explanatory
paragraphs.
3. Confirm each multiline finding is separated from the next numbered
finding by one blank line.
4. Confirm wrapped rows of each indented paragraph remain aligned
beneath the finding body, rather than returning to the left edge.
5. Render a short one-line numbered or unordered list and confirm its
items remain compact without added blank rows.

Targeted tests:

- `just test -p codex-tui history_cell insert_history markdown_render
markdown_stream streaming::controller`
- `just argument-comment-lint-from-source -p codex-tui`

## Related Work

PR #24346 changes Markdown table column allocation in parallel. This PR
is intentionally limited to list-item readability and history wrapping;
both branches touch `codex-rs/tui/src/markdown_render.rs`, so a small
merge conflict may need resolution depending on merge order.
2026-05-25 15:42:28 -03:00
Felipe CouryandGitHub 20d1b7674d fix(tui): improve markdown table column allocation (#24346)
## Why

Markdown tables with a long path-heavy column could allocate almost all
available width to that column and collapse neighboring prose columns to
only a few characters. In rollout summaries this made `Unit` and `What
It Adds` difficult to read, even though the long `Files` values were the
content best suited to wrapping.

The affected example also specified `Files` as right aligned in its
markdown delimiter (`---:`). This change preserves that requested
alignment while improving how width is distributed.

| Before | After |
|---|---|
| <img width="1709" height="764" alt="image"
src="https://github.com/user-attachments/assets/932ab21c-b72d-48a2-9aad-b69da87a0968"
/> | <img width="1711" height="855" alt="image"
src="https://github.com/user-attachments/assets/4028bd20-2228-4c2f-be8a-1866325b7f62"
/> |


## What Changed

- Classify table columns as narrative, token-heavy, or compact during
width allocation.
- Shrink token-heavy path and URL columns before shrinking narrative
prose, while preserving compact counts and short labels longest.
- Use readable soft floors for narrative and token-heavy content before
falling back to tighter layouts.
- Add snapshot coverage for a rollout-shaped table containing
right-aligned file paths and prose columns.

## How to Test

1. Render a markdown table with `Unit`, right-aligned `Files`, `Adds`,
`Removes`, and `What It Adds` columns at a constrained terminal width.
2. Put long repository paths in `Files` and sentence-length content in
`Unit` and `What It Adds`.
3. Confirm that `Files` remains right aligned but wraps before the
narrative columns become unreadable.
4. Confirm that the compact numeric columns remain easy to scan.

Targeted tests:
- `just test -p codex-tui markdown_render`

Validation note: `just test -p codex-tui` was also attempted and reached
two existing unrelated failures in
`app::tests::update_feature_flags_disabling_guardian_*`; the markdown
rendering regression test passes in the targeted run.
2026-05-25 15:09:17 -03:00
Eric TrautandGitHub 613e5149a4 TUI config cleanup: MCP inventory (#24265)
## Summary

The TUI `/mcp` inventory flow should reflect the app server’s MCP status
response. It was also joining those results with the TUI process’s local
`config.mcp_servers`, which can diverge once MCP state is owned by a
remote app server and cause stale local command, URL, status, or
empty-state details to render.

This change removes the local config join from the app-server-backed
inventory renderer. The TUI now renders directly from the existing
`mcpServerStatus/list` payload and treats an empty status response as
the empty MCP inventory state.

## Known limitation

The existing `mcpServerStatus/list` payload does not include
disabled-state or disabled-reason fields. To preserve the current
app-server API, this PR does not try to infer that state from
client-local config. If remote `/mcp` needs to show disabled/reason
details again, that should come from app-server-owned status data in a
follow-up.

Related to #22914, #22915, and #22916.
2026-05-25 09:56:21 -07:00
Eric TrautandGitHub bb55736906 TUI config cleanup: trusted projects (#24255)
## Why
TUI onboarding trusted-project persistence should go through the same
app-server config write path as other config mutations. Writing
`config.toml` directly from the trust widget bypasses that layer and can
let onboarding proceed even when the trust decision was not actually
persisted.

## What changed
- Added a TUI config helper that writes the existing project trust
structure through `config/batchWrite`.
- Persists trust decisions as `projects.<project>.trust_level =
"trusted"` using the existing project trust key helper.
- Changed the trust directory widget to only record the user selection;
onboarding performs the app-server write before reporting success.
- Keeps the user on the trust screen and shows an error if app-server
persistence fails.

## Verification
- `cargo test -p codex-tui --lib
trust_persistence_failure_keeps_trust_step_in_progress`
- `cargo test -p codex-tui --lib
trusted_project_edit_targets_project_trust_level`
- Manual: built the local `codex-cli`, accepted the trust prompt in a
temp project, confirmed `projects.<project>.trust_level = "trusted"`,
and simulated an unwritable config to verify onboarding stays on the
trust screen without writing trust.
2026-05-25 09:54:05 -07:00
Eric TrautandGitHub f05fd0e661 TUI config cleanup: oss_provider (#24254)
## Summary

Manual provider selection during `codex --oss` startup was still
persisting `oss_provider` through the legacy local `config.toml` writer.
That bypasses the app-server-owned config mutation path used by the TUI,
so this routes the write through the app server config API instead.

The net behavior is intentionally narrow: only an interactive picker
selection is persisted. Auto-detected single-running-provider startup
and explicit `--local-provider` startup remain ephemeral, so merely
having one backend running does not make that provider sticky for future
runs.

## What Changed

- Removed the TUI picker’s direct dependency on
`set_default_oss_provider`.
- Had `oss_selection` report whether the returned provider came from the
interactive picker.
- Carried only manually selected providers into startup persistence.
- Wrote `oss_provider` via `config/batchWrite` once the app server
session is available.
- Logged a warning and continued startup if the app-server config write
fails.

## Verification

Manually smoke-tested the real `codex-tui` binary with a temporary
`CODEX_HOME`, pseudo-terminal input, and a fake LM Studio HTTP server:

- Interactive picker selection persisted `oss_provider = "lmstudio"`.
- Non-picker `--local-provider lmstudio` startup did not persist
`oss_provider`.
2026-05-25 09:53:39 -07:00
Eric TrautandGitHub 5fb5e47767 Respect hook trust bypass during TUI startup (#24317)
Fixes #24093.

## Why

`--dangerously-bypass-hook-trust` is a supported CLI flag intended for
headless or automated runs where enabled hooks should be allowed to run
without requiring persisted trust. In the TUI, startup hook review still
opened whenever hooks looked untrusted, so a launch using the bypass
could block on the interactive "Hooks need review" prompt.

The tricky case is persistent app-server resume: a resume may attach to
an already-running thread, where resume config overrides are ignored. In
that path, hiding the startup review would be wrong because the existing
hook engine may still filter untrusted hooks.

## What Changed

- Startup hook review now skips the prompt only when hook trust bypass
is actually safe for that launch.
- The TUI forwards `bypass_hook_trust` through the app-server request
config for fresh thread start/resume/fork paths, and the app-server
applies it as a runtime-only `ConfigOverrides` value rather than
treating it like a `config.toml` setting.
- Persistent app-server resumes keep the startup review prompt so users
still have a chance to trust hooks when the running thread cannot
receive the bypass override.

## Verification

- Added focused coverage for startup hook review with and without
`bypass_hook_trust`.
- Extended existing TUI/app-server config override tests to cover
forwarding and applying `bypass_hook_trust`.
2026-05-25 09:44:21 -07:00
Eric TrautandGitHub 913270a689 Show remote connection details in /status (#24420)
## Summary

Fixes #24411.

`/status` currently has no way to show when the TUI is talking to Codex
through a remote transport. That makes embedded local sessions, local
daemon sessions, and true remote sessions look the same, and it hides
the remote server version when debugging connection-specific behavior.

This PR adds a single `Remote` row for non-embedded connections only.
The row shows the sanitized connection address and a dimmed version
parenthetical, preserving the existing status output for embedded local
sessions.

<img width="791" height="144" alt="image"
src="https://github.com/user-attachments/assets/529d7940-1c45-4586-8b06-f20a1f04b771"
/>


## Verification

- Manually validated when connecting remotely (either implicitly to
local daemon or explicitly)
2026-05-25 09:42:42 -07:00
Eric TrautandGitHub caebff3d66 tui: label compact rate-limit percentages (#24314)
## Summary

The compact TUI status line already renders rate-limit percentages as
remaining capacity, but the text did not say so. That made high-usage
red indicators ambiguous because values like `weekly 6%` could be read
as either used or remaining.

This PR labels the compact rate-limit values explicitly as `left` across
the status line, terminal title, and setup previews.

Addresses #24274
2026-05-25 09:41:32 -07:00
Felipe CouryandGitHub acd851e89f fix(tui): restore Windows VT before TUI renders (#24082)
## Why

Older Git for Windows versions can leave the Windows console output mode
without virtual terminal processing after Codex runs git metadata
commands in a repository. When the TUI later emits ANSI control
sequences for redraws, restore, or image rendering, Windows Terminal can
show raw escape bytes or leave the prompt/status area corrupted.

This is a targeted mitigation for the repo-conditioned Windows rendering
corruption reported in #23888 and related reports #23512 and #23628.
Updating Git avoids the trigger for affected users, but Codex should
also reassert the terminal mode before it writes TUI control sequences.

| Before | After |
|---|---|
| <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23 21"
src="https://github.com/user-attachments/assets/3218c379-5f97-4c71-ab25-805c9d20578a"
/> | <img width="2100" height="1359" alt="CleanShot 2026-05-22 at 11 23
58"
src="https://github.com/user-attachments/assets/55ac72bb-37d0-400e-99bc-12dd5ea4092d"
/> |


## What Changed

- Re-enable Windows virtual terminal processing for stdout and stderr
before TUI mode setup, restore, redraw, resume, and pet image render
paths.
- Treat invalid, null, or non-console handles as no-ops so redirected or
non-console output is unaffected.
- Keep the helper as a no-op on non-Windows platforms.

## How to Test

1. On Windows Terminal with a Git 2.28.0 for Windows install, start
Codex inside a valid Git repository.
2. Start a new Codex CLI session.
3. Confirm the prompt, working indicator, and bottom status line remain
readable instead of showing raw ANSI escape sequences.
4. Repeat outside a Git repository to confirm the ordinary non-repo
startup path is unchanged.

Targeted tests:
- Not run locally; the behavior depends on Windows console mode APIs and
the current worktree is on macOS.
2026-05-22 16:20:09 -03:00
Michael BolinandGitHub 75b7e06621 docs: update README.md to mention curl-based installer (#24106)
Now that users can install via `curl` (or `irm`), we should tell them
about it so they no longer need to use `npm`!

Note that on one Windows machine I tested on, when I ran:

```
irm https://chatgpt.com/codex/install.ps1 | iex
```

I got this error:

```
iex : The property 'OSArchitecture' cannot be found on this object. Verify that the property exists.
At line:1 char:45
+ irm https://chatgpt.com/codex/install.ps1 | iex
+                                             ~~~
    + CategoryInfo          : NotSpecified: (:) [Invoke-Expression], PropertyNotFoundException
    + FullyQualifiedErrorId : PropertyNotFoundStrict,Microsoft.PowerShell.Commands.InvokeExpressionCommand
```

so we'll recommend the following that works from both `cmd.exe` and
PowerShell:

```
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
```

This PR makes a slight update to `codex-rs/tui/src/update_action.rs` to
match.
2026-05-22 18:39:08 +00:00
adams-oaiandGitHub 865ca936db Add new enterprise requirement gate (#23736)
Add new enterprise requirement gate.

Validation:
- `cargo test -p codex-config --lib`
- `cargo test -p codex-app-server-protocol --lib`
- `cargo test -p codex-tui --lib debug_config`
- `cargo test -p codex-app-server --lib` *(fails: stack overflow in
`in_process::tests::in_process_start_initializes_and_handles_typed_v2_request`;
reproduces when run alone)*
2026-05-22 11:33:44 -07:00
jif-oaiandGitHub f55f864b9f tui: make codex-tui.log opt-in (#24081)
## Why

The TUI currently creates a shared plaintext `codex-tui.log` under the
default log directory. That append-only file can keep growing across
runs even though the TUI already records diagnostics in bounded local
stores.

Make the plaintext file log an explicit troubleshooting choice instead
of a default side effect.

This is possible because logs are also stored in the DB with proper
rotation

## What changed

- Only install the TUI file logging layer when `log_dir` is explicitly
set.
- Remove the prior `codex-tui.log` at startup before an opt-in file
layer is created.
- Clarify the `log_dir` config/schema text and `docs/install.md` example
so users opt in with `codex -c log_dir=...` when they need a plaintext
log.
2026-05-22 17:19:51 +00:00
jif-oaiandGitHub 2c6605ab35 config: remove legacy profile write paths (#24055)
## Why

[#23883](https://github.com/openai/codex/pull/23883) moved the
user-facing `--profile` flag onto profile v2 and
[#23886](https://github.com/openai/codex/pull/23886) removed CLI
forwarding for the legacy profile-v1 path. Core and TUI config
persistence still carried `active_profile` and
`ConfigEditsBuilder::with_profile`, which let later writes continue
targeting legacy `[profiles.<name>]` tables after profile selection
moved to profile-v2 config files.

## What

- Remove legacy profile routing from
[`ConfigEditsBuilder`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/core/src/config/edit.rs#L1064-L1294),
so core config edits no longer carry `with_profile` or infer
`[profiles.*]` write targets from a `profile` key.
- Drop `active_profile` plumbing from runtime `Config`, TUI
startup/state, app-server config override forwarding, and Windows
sandbox setup persistence.
- Make app-server-backed TUI config edits use unscoped model,
service-tier, feature, Auto-review, plan-mode, and Windows sandbox paths
through
[`tui/src/config_update.rs`](https://github.com/openai/codex/blob/4b38e9c22e762261d7f7eef49d8a21792e241a06/codex-rs/tui/src/config_update.rs#L43-L112).
- Update config edit coverage so legacy `profile` state stays untouched
by direct model writes, and remove tests whose only contract was the
deleted profile-scoped persistence path.

## Testing

- Not run locally.
2026-05-22 12:50:42 +02:00
jif-oaiandGitHub fd72e99384 config: remove legacy profile v1 resolution (#24051)
## Why

[#23883](https://github.com/openai/codex/pull/23883) moved user-facing
`--profile` selection onto profile v2, and
[#23886](https://github.com/openai/codex/pull/23886) removed the old CLI
`config_profile` override path. Core still had a second legacy path:
`profile = "..."` could select `[profiles.*]` values while runtime
config was built. Keeping that resolver alive preserves the old
precedence model and profile-carrying surfaces even though profile
selection now points at `$CODEX_HOME/<name>.config.toml`.

## What

- Reject legacy top-level `profile = "..."` config while loading runtime
config, with an error that points callers at `--profile <name>` and
`<name>.config.toml` in the [core load
path](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2524-L2531).
- Remove the remaining profile-v1 merge points from runtime config
resolution, including features, permissions, model/provider selection,
web search, Windows sandbox settings, TUI settings, role reloads, and
OSS provider lookup.
- Drop the leftover profile override surface from
[`ConfigOverrides`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2118-L2148)
and from the MCP server `codex` tool schema.
- Prune profile-precedence tests that only exercised the removed
resolver and replace them with rejection coverage for the legacy
selector.

## Testing

- Not run in this metadata pass.
- Added
[`legacy_profile_selection_is_rejected`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/config_tests.rs#L7942-L7965)
coverage for the new runtime guard.
2026-05-22 12:13:52 +02:00
Eric TrautandGitHub 5a6e905994 Fix auto-review permission profile override (#23956)
## Summary
The auto-review runtime sync path was assigning a raw
`PermissionProfile` into `runtime_permission_profile_override`, whose
field now expects `RuntimePermissionProfileOverride`. That broke the TUI
Bazel build.

This changes the assignment to store
`RuntimePermissionProfileOverride::from_config(&self.config)`, matching
the other runtime override paths and preserving the active profile and
network metadata with the permission profile.
2026-05-21 16:52:36 -07:00
Eric TrautandGitHub e8378c7f0c [3 of 4] tui: route feature and memory toggles through app server (#22915)
## Why
Experimental feature toggles and memory settings can update several
related config values in one interaction. Keeping those writes local in
a remote TUI session is especially dangerous because the UI can diverge
from the app-server config while also leaving behind partially stale
supporting keys.

This is **[3 of 4]** in a stacked series that moves TUI-owned config
mutations onto app-server APIs.

## What changed
- Routed feature flag persistence through app-server batch writes,
including the supporting reviewer and permission updates used by
guardian approval.
- Routed Windows sandbox mode persistence and legacy Windows feature
cleanup through app-server writes.
- Routed memory settings through app-server batch writes and updated the
TUI tests to exercise the embedded app-server path.

## Config keys affected
- `features.<feature_key>`
- `profiles.<profile>.features.<feature_key>`
- `approval_policy`
- `sandbox_mode`
- `approvals_reviewer`
- `windows.sandbox`
- `features.experimental_windows_sandbox`
- `features.elevated_windows_sandbox`
- `features.enable_experimental_windows_sandbox`
- Profile-scoped Windows legacy feature variants under
`profiles.<profile>.features.*`
- `memories.use_memories`
- `memories.generate_memories`
- Profile-scoped memory variants under `profiles.<profile>.memories.*`

## Suggested manual validation
- Connect the TUI to a remote app server, toggle guardian approval on
and off, and confirm the remote config updates
`features.guardian_approval`, reviewer state, approval policy, and
sandbox mode coherently.
- Toggle a default-false experimental feature at the root level, disable
it again, and confirm the key clears instead of lingering as an
unnecessary explicit `false`.
- Change memory settings and confirm the remote config updates both
memory keys while the running TUI reflects the new state.
- On Windows, switch sandbox mode through the TUI and confirm
`windows.sandbox` is updated while the legacy Windows feature keys are
cleared.

## Stack
1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
primary settings writes
2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
and skill enablement
3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
feature and memory toggles
4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
startup and onboarding bookkeeping
2026-05-21 16:03:11 -07:00
Eric TrautandGitHub 4acb456bfe TUI: skip goal replace prompt for completed goals (#23792)
## Why
Users reported that the replacement confirmation feels unnecessary when
the current thread goal is already complete. In that state, `/goal
<objective>` is starting fresh rather than interrupting active work.

## What changed
`/goal <objective>` now skips the replace confirmation when the existing
goal has `complete` status and uses the existing fresh replacement path.
Goals that are active, paused, blocked, usage-limited, or budget-limited
still require confirmation before being replaced.
2026-05-21 10:45:43 -07:00
Eric TrautandGitHub b132fec000 Improve /goal error messages for ephemeral sessions (#23796)
## Why

When a user runs `/goal` in a temporary session, the TUI can currently
surface an internal app-server failure such as `thread/goal/get failed
in TUI`. That message is technically true, but it does not explain the
actual constraint: goals require a saved session because goal state is
persisted with the thread.

This is especially confusing when `codex doctor` reports the background
app-server as running in ephemeral mode, since that wording is easy to
conflate with ephemeral thread/session behavior.

## What changed

- Added a TUI-side formatter for thread-goal RPC failures in
`codex-rs/tui/src/app/thread_goal_actions.rs`.
- Detects app-server/core errors that indicate goals are unsupported for
an ephemeral thread/session.
- Replaces the internal RPC failure with a user-facing explanation:

```text
Goals need a saved session. This session is temporary.
Run `codex` to start a saved session, or `codex resume` / `/resume` to reopen one.
```

- Preserves the existing generic failure wording for non-ephemeral goal
errors.

## Verification

- `cargo test -p codex-tui thread_goal_error_message --lib`

I also tried `cargo test -p codex-tui`; it built successfully but the
test runner aborted in an unrelated side-thread stack overflow
(`app::tests::discard_side_thread_removes_agent_navigation_entry`),
which reproduced when run by itself.
2026-05-21 09:33:17 -07:00
viyatb-oaiandGitHub fcff0d6c52 tui: plumb permission profile selection (#23708)
## Why

The named-profile `/permissions` picker needs a small TUI action path
that can select permission profiles without folding the menu UI and
profile metadata into the same review.

## What changed

- Carry permission-profile selections through the TUI app event flow.
- Persist selected profiles while preserving the existing approval
settings and guardrail prompts.
- Keep the legacy `/permissions` picker behavior in this layer; the
profile-mode menu stays in the follow-up PR.

## Stack

1. [#22931](https://github.com/openai/codex/pull/22931):
runtime/session/network propagation for active permission profiles.
2. **This PR**: TUI selection plumbing and guardrail flow.
3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
`/permissions` menu and custom profile display.

<img width="1632" height="1186" alt="image"
src="https://github.com/user-attachments/assets/69ddcd5e-b57c-468d-8c1d-246916323c15"
/>

## Validation

- `git diff --cached --check` before commit.
- Full test run skipped at the user request while pushing the split
stack.
2026-05-21 12:26:36 -03:00
jif-oaiandGitHub e0e304b123 cli: remove legacy profile v1 plumbing (#23886)
## Why

[#23883](https://github.com/openai/codex/pull/23883) moved the
user-facing `--profile` flag onto profile v2. The shared CLI option
layer still carried the old `config_profile` slot and several CLI
entrypoints still copied that value into legacy config overrides.
Leaving that path around makes the CLI surface look like it still
selects legacy `[profiles.*]` state even though `--profile` now means
`$CODEX_HOME/<name>.config.toml`.

## What

- Remove the legacy `config_profile` field and merge/copy path from
[`SharedCliOptions`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/utils/cli/src/shared_options.rs#L8-L177).
- Stop forwarding profile-v1 overrides from CLI, exec, TUI, doctor,
debug, feature, and exec-server paths; runtime profile selection remains
on `config_profile_v2` through
[`loader_overrides_for_profile`](https://github.com/openai/codex/blob/95baaf72920c8db22097df8d15a0bb76c84528b6/codex-rs/cli/src/main.rs#L1606-L1619).
- Resolve local OSS provider selection from the base config in exec and
TUI now that the legacy profile argument is gone.

## Testing

- Not run (cleanup-only follow-up to #23883).
2026-05-21 17:21:37 +02:00
viyatb-oaiandGitHub 713a5b1b00 feat: support managed permission profiles in requirements.toml (#23433)
## Why

Cloud-managed `requirements.toml` should be able to define the managed
permission profiles a client may select and constrain that selectable
set without requiring local user config to recreate the profile catalog.

This keeps requirements focused on restrictions. The selected default
remains a config or session choice, while requirements contribute the
managed profile bodies and `allowed_permissions` allowlist that the
config-loading boundary validates before a resolved runtime
`PermissionProfile` is installed.

## What changed

- Add `requirements.toml` support for a managed permission-profile
catalog plus its allowlist:

```toml
allowed_permissions = ["review", "build"]

[permissions.review]
extends = ":read-only"

[permissions.build]
extends = ":workspace"
```

- Merge requirements-defined profile bodies into the effective
permission catalog and reject profile ids that collide with
config-defined profiles.
- Validate that every `allowed_permissions` entry resolves to a built-in
or catalog profile before selection uses it.
- Preserve allowed configured named-profile selections. When a
configured named profile is disallowed, fall back to the first allowed
requirements profile with a startup warning.
- Keep built-in selections and the stock trust-based `:read-only` /
`:workspace` fallback path intact when no permission profile is
explicitly selected.
- Centralize the managed catalog and allowlist selection path in
`EffectivePermissionSelection` so the requirements boundary is visible
in config loading.
- Surface `allowedPermissions` through `configRequirements/read`, and
update the generated app-server schema fixtures plus the app-server
README.

## Validation

- `cargo test -p codex-config`
- `cargo test -p codex-core system_requirements_`
- `cargo test -p codex-core system_allowed_permissions_`
- `cargo test -p codex-app-server-protocol`
- `just write-app-server-schema`

## Related work

- Uses merged permission-profile inheritance support from #22270 and
#23705.
- Kept separate from the in-flight permission profile listing API in
#23412.
2026-05-20 17:33:01 -07:00
Matthew ZengandGitHub 0a4179bb19 [codex] Add plugin id to MCP tool call items (#23737)
Add owning plugin id to MCP tool call items so we can better filter them
at plugin level.

## Summary
- add optional `plugin_id` to MCP tool-call items and legacy begin/end
events
- propagate plugin metadata into emitted core items and app-server v2
`ThreadItem::McpToolCall`
- preserve plugin ids through app-server replay/redaction paths and
regenerate v2 schema fixtures

## Testing
- `just write-app-server-schema`
- `just fmt`
- `just fix -p codex-core`
- `cargo test -p codex-protocol -p codex-app-server-protocol`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core mcp_tool_call_item_includes_plugin_id --lib`
- `cargo check -p codex-tui --tests`
- `cargo check -p codex-app-server --tests`
- `git diff --check`

## Notes
- `just fix -p codex-core` completed with two non-fatal
`too_many_arguments` warnings on the touched MCP notification helpers.
- A broader `cargo test -p codex-core` run passed core unit tests, then
hit shell/sandbox/snapshot failures in the integration target.
- A broader app-server downstream run hit the existing
`in_process::tests::in_process_start_clamps_zero_channel_capacity` stack
overflow; `cargo test -p codex-exec` also hit the existing sandbox
expectation mismatch in
`thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile`.
2026-05-20 17:02:10 -07:00
Shijie RaoandGitHub 370b13afc9 Honor client-resolved service tier defaults (#23537)
## Why

Model catalog responses can now advertise a nullable
`default_service_tier` for each model. Codex needs to preserve three
distinct states all the way from config/app-server inputs to inference:

- no explicit service tier, so the client may apply the current model
catalog default when FastMode is enabled
- explicit `default`, meaning the user intentionally wants standard
routing
- explicit catalog tier ids such as `priority`, `flex`, or future tiers

Keeping those states distinct prevents the UI from showing one tier
while core sends another, especially after model switches or app-server
`thread/start` / `turn/start` updates.

## What Changed

- Plumbed `default_service_tier` through model catalog protocol types,
app-server model responses, generated schemas, model cache fixtures, and
provider/model-manager conversions.
- Added the request-only `default` service tier sentinel and normalized
legacy config spelling so `fast` in `config.toml` still materializes as
the runtime/request id `priority`.
- Moved catalog default resolution to the TUI/client side, including
recomputing the effective service tier when model/FastMode-dependent
surfaces change.
- Updated app-server thread lifecycle config construction so
`serviceTier: null` preserves explicit standard-routing intent by
mapping to `default` instead of internal `None`.
- Kept core responsible for validating explicit tiers against the
current model and stripping `default` before `/v1/responses`, without
applying catalog defaults itself.

## Validation

- `CARGO_INCREMENTAL=0 cargo build -p codex-cli`
- `CARGO_INCREMENTAL=0 cargo test -p codex-app-server model_list`
- `cargo test -p codex-tui service_tier`
- `cargo test -p codex-protocol service_tier_for_request`
- `cargo test -p codex-core get_service_tier`
- `RUST_MIN_STACK=8388608 CARGO_INCREMENTAL=0 cargo test -p codex-core
service_tier`
2026-05-20 15:57:50 -07:00
AbhinavandGitHub eee3e60db3 Add SubagentStop hook (#22873)
# What

<img width="1792" height="1024" alt="image"
src="https://github.com/user-attachments/assets/8f81d232-5813-4994-a61d-e42a05a93a3e"
/>

`SubagentStop` runs when a thread-spawned subagent turn is about to
finish. Thread-spawned subagents use `SubagentStop` instead of the
normal root-agent `Stop` hook.

Configured handlers match on `agent_type`. Hook input includes the
normal stop fields plus:

- `agent_id`: the child thread id.
- `agent_type`: the resolved subagent type.
- `agent_transcript_path`: the child subagent transcript path.
- `transcript_path`: the parent thread transcript path.
- `last_assistant_message`: the final assistant message from the child
turn, when available.
- `stop_hook_active`: `true` when the child is already continuing
because an earlier stop-like hook blocked completion.

`SubagentStop` shares the same completion-control semantics as `Stop`,
scoped to the child turn:

- No decision allows the child turn to finish.
- `decision: "block"` with a non-empty `reason` records that reason as
hook feedback and continues the child with that prompt.
- `continue: false` stops the child turn. If `stopReason` is present,
Codex surfaces it as the stop reason.

# Lifecycle Scope

Only thread-spawned subagents run `SubagentStop`.

Internal/system subagents such as Review, Compact, MemoryConsolidation,
and Other do not run normal `Stop` hooks and do not run `SubagentStop`.
This avoids exposing synthetic matcher labels for internal
implementation paths.

# Stack

1. #22782: add `SubagentStart`.
2. This PR: add `SubagentStop`.
3. #22882: add subagent identity to normal hook inputs.
2026-05-20 14:59:41 -07:00
viyatb-oaiandGitHub 40ad7be2b5 core: refresh active permission profiles at runtime (#22931)
## Why

Once a named permission profile is selected, runtime state has to keep
that profile identity intact instead of collapsing back to anonymous
effective permissions. The session refresh path also needs to rebuild
profile-derived network proxy state so active profile switches take
effect consistently.

## What changed

- Preserve the active permission profile through session updates.
- Rebuild profile-derived runtime/network configuration when the active
profile changes.
- Keep the runtime path aligned with the current session configuration
APIs.
- Tighten the affected tests, including the Windows delete-pending
memory-file case that was intermittently tripping CI.

## Stack

1. **This PR**: runtime/session/network propagation for active
permission profiles.
2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection
plumbing and guardrail flow.
3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
`/permissions` menu and custom profile display.

<img width="1296" height="906" alt="image"
src="https://github.com/user-attachments/assets/077fa3a7-80cb-4925-80b1-d2395018d90a"
/>
2026-05-20 21:55:21 +00:00
Eric TrautandGitHub 7c3cc1db81 Fix thread settings clippy failure (#23724)
## Why

`main` picked up two small Rust build failures after nearby merges:

- #23507 added a real handler for
`ServerNotification::ThreadSettingsUpdated`, but the same variant was
still listed in the ignored-notification match arm. Full Clippy runs
treat the resulting unreachable-pattern warning as an error.
- #23666 added `turn_id` and `truncation_policy` to
`codex_tools::ToolCall`, while the goal extension backend test fixtures
from the goal-extension work still used the old shape. That left
`codex-goal-extension` tests unable to compile once the branches met on
`main`.

## What changed

Removed the duplicate `ThreadSettingsUpdated` match pattern from
`tui/src/chatwidget/protocol.rs`.

Updated the goal extension test `tool_call` helper to populate the new
`ToolCall` fields, and reused that helper for the one direct literal
that still had the old field list.

## Verification

- `just fix -p codex-tui`
- `cargo test -p codex-goal-extension`
2026-05-20 11:58:23 -07:00
Eric TrautandGitHub edc48e4612 Sync TUI thread settings through app server (#23507)
Builds on #23502.

## Why

#23502 adds the app-server `thread/settings/update` API and matching
`thread/settings/updated` notification. The TUI already lets users
change thread-scoped settings such as model, reasoning effort, service
tier, approvals, permissions, personality, and collaboration mode, but
those updates need to flow through the app server so embedded and
connected clients observe the same thread state.

This is a rework (simplification) of PR
https://github.com/openai/codex/pull/22510. It has the same
functionality, but the underlying `thread/settings/update` api is now
simpler in that it no longer returns the effective settings as a
response. Now, clients receive the effective settings only through the
`thread/settings/updated` notification.

## What Changed

This updates the TUI to send `thread/settings/update` whenever those
thread-scoped settings change and to treat the RPC response as the
authoritative acknowledgement. It also routes `thread/settings/updated`
notifications back into cached session state and the visible chat widget
so active and inactive threads stay in sync after app-server-originated
changes.

The implementation is kept to the TUI layer: settings conversion and
merge logic live under `codex-rs/tui/src/app/thread_settings.rs`, with
dispatch/routing hooks in the existing app and chat widget paths.

## Verification

I manually tested using `codex app-server --listen unix://` and then
launching two copies of the TUI that use the same local app server. I
then resumed the same thread on both and verified that changes like plan
mode, fast mode, model, reasoning effort, etc. are reflected "live" in
the second client when modified in the first and vice versa.
2026-05-20 11:05:14 -07:00
Eric TrautandGitHub 771a4e74ac Add thread/settings/update app-server API (#23502)
## Why

App-server clients need a way to update a thread's next-turn settings
without starting a turn, adding transcript content, or waiting for turn
lifecycle events. This gives settings UI a direct path for durable
thread settings while clients observe the eventual effective state
through a notification.

This is a simplified rework of PR
https://github.com/openai/codex/pull/22509. In particular, it changes
the `thread/settings/update` api to return immediately rather than
waiting and returning the effective (updated) thread settings. This
makes the new api consistent with `turn/start` and greatly reduces the
complexity of the implementation relative to the earlier attempt.

## What Changed

- Adds experimental `thread/settings/update` with partial-update request
fields and an empty acknowledgment response.
- Adds experimental `thread/settings/updated`, carrying full effective
`ThreadSettings` and scoped by `threadId` to subscribed clients for the
affected thread.
- Shares durable settings validation with `turn/start`, including
`sandboxPolicy` plus `permissions` rejection and `serviceTier: null`
clearing.
- Emits the same settings notification when `turn/start` overrides
change the stored effective thread settings.
- Regenerates app-server protocol schema fixtures and updates
`app-server/README.md`.
2026-05-20 11:03:20 -07:00
Eric TrautandGitHub c0f7e1b99f [2 of 2] Start fresh TUI thread in background (#23176)
## Why

After the terminal-probe work in #23175, fresh-session startup still
waits for `thread/start` before the chat input can become usable. The
chat widget already has the machinery to hold early submissions until a
session is configured, so fresh `thread/start` does not need to stay on
the input-ready hot path.

Refs #16335.

## What

This PR starts fresh app-server threads in a background task, reports
completion through a startup app event, and attaches the primary session
once `thread/start` returns. Resume and fork startup paths remain
synchronous.

## Benchmark

In the local pty startup benchmark, this PR's pre-optimization base
branch, #23175, measured about 152ms median from launch to accepted chat
input. The stacked result measured about 66ms median, for an approximate
additional savings of 85-95ms. For broader context, the original `main`
baseline before either startup optimization was about 250.5ms median. We
also measured Codex 0.117.0 on the same machine at about 64.6ms median,
so the stacked branch is back in the old-startup-time range.

## Stack

1. [#23175: [1 of 2] Optimize TUI startup terminal
probes](https://github.com/openai/codex/pull/23175) — base PR
2. [#23176: [2 of 2] Start fresh TUI thread in
background](https://github.com/openai/codex/pull/23176) — this PR

## Verification

- `cargo test -p codex-tui`
2026-05-20 10:00:33 -07:00
anp-oaiandGitHub f198ca115b feat: Add btw alias for side slash command (#23592) 2026-05-20 15:49:35 +00:00
Michael BolinandGitHub cfa16fcc2e runtime: detect Codex package layout (#23596)
## Why

The package-builder stack now creates a canonical Codex package
directory where the entrypoint lives under `bin/`, bundled helper
resources live under `codex-resources/`, and bundled PATH-style tools
live under `codex-path/`. That layout is not specific to the standalone
installer: npm, brew, install scripts, and manually unpacked artifacts
should all be able to use the same package shape.

The Rust runtime still only knew about the legacy standalone release
layout, where resources sit next to the executable. A packaged binary
therefore would not identify its package root or prefer the bundled `rg`
from `codex-path/`.

## What changed

- Adds `CodexPackageLayout` to `codex-install-context` and detects it
from an executable path shaped like `<package>/bin/<entrypoint>` when
`<package>/codex-package.json` is present.
- Splits `InstallContext` into an install `method` plus an optional
package layout so the layout is shared across npm, bun, brew,
standalone, and other launch contexts.
- Stores package-layout paths as `AbsolutePathBuf` values.
- Keeps `codex-resources/` and `codex-path/` optional so Codex can still
run with degraded behavior if sidecar directories are missing.
- Updates `InstallContext::rg_command()` to prefer bundled
`codex-path/rg` or `rg.exe`, then fall back to the legacy standalone
resources location, then system `rg`.
- Updates `codex doctor` reporting so package installs show package,
bin, resources, and path directories, and so bundled search detection
recognizes `codex-path/` for any install method.

## Test plan

- `cargo test -p codex-install-context`
- `cargo test -p codex-cli`
- `cargo test -p codex-tui
update_action::tests::maps_install_context_to_update_action`
- `just bazel-lock-check`
2026-05-19 23:13:49 -07:00
Eric TrautandGitHub e43a2e297f Fix stale background terminal poll events (#23231)
## Why

Issue #23214 reports `/ps` showing no background terminals while the
status line still says it is waiting for a background terminal. The race
is in core: `write_stdin` can poll a process that exits before the
response returns. The process manager correctly returns `process_id:
None`, but the handler still emitted a `TerminalInteraction` event using
the requested session id, causing clients to believe a dead process was
still being polled.

Fixes #23214.

## What changed

- Suppress `TerminalInteraction` events for empty `write_stdin` polls
once `response.process_id` is `None`.
- Continue emitting interactions for non-empty stdin, even if that input
causes the process to exit before the response returns.
- Extend the unified exec integration test to assert completed empty
polls do not emit terminal interactions.

## Verification

- `cargo test -p codex-core --test all
unified_exec_emits_one_begin_and_one_end_event`
- `cargo test -p codex-core --test all
unified_exec_emits_terminal_interaction_for_write_stdin`

`cargo test -p codex-core` currently aborts in unrelated
`agent::control::tests::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
with a reproducible stack overflow.
2026-05-19 20:48:37 -07:00
canvrno-oaiandGitHub 27c4c67b15 Fix: TUI starting in wrong CWD (#23538)
This fixes a regression wher codex could start in the wrong directory
when a live local app-server socket was present. The issue was that
implicit local socket reuse was being treated like an explicit remote
workspace session, which dropped the invoking cwd unless --cd was
passed.

The change separates local socket transport from true remote workspace
semantics.
- Plain local startup keeps local cwd, trust, resume, picker, and
config-refresh behavior.
- Explicit --remote keeps the existing remote cwd behavior.
- Added coverage for launch target selection and local-session
filtering/cwd behavior.

Steps to test:
- Start a local app-server from a different directory than the repo you
want to use.
  - Launch codex from a project/worktree without --cd.
- Confirm the session starts in the invoking directory, not the
app-server process directory.
- Confirm explicit codex --remote ... still preserves existing remote
behavior.
2026-05-19 15:48:40 -07:00
adams-oaiandGitHub d86352d520 Add CUA requirements subsection for locked computer use (#23555)
Adds a new top-level section for "CUA" requirements that can allow for
disablement of specific features as needed for enterprises.
2026-05-19 15:41:44 -07:00
Felipe CouryandGitHub 40be41763c fix(tui): preserve modified enter in plan questions (#23536)
## Why

Plan mode questionnaires reuse the shared composer for free-form
answers, but the surrounding `request_user_input` overlay still treated
every `KeyCode::Enter` as “advance to the next question.” That made
`Shift+Enter` insert a newline in the composer and then immediately
advance the questionnaire anyway.

Fixes #23448.

## What Changed

- pass the live `RuntimeKeymap` into `RequestUserInputOverlay` so its
embedded composer honors existing `/keymap` composer/editor remaps
- advance free-form questions only on the configured composer submit
binding, instead of any Enter-shaped key event
- add regressions for `Shift+Enter` newline behavior and configured
composer submit bindings inside the questionnaire UI

## How to Test

1. Start Codex in Plan mode and trigger a `request_user_input`
questionnaire with a free-form answer field.
2. Focus the free-form field, type a line, then press `Shift+Enter`.
3. Confirm the answer gains a newline and the questionnaire stays on the
same question.
4. Press the configured submit binding, or plain `Enter` with the
default keymap, and confirm the questionnaire advances as before.

Targeted tests:
- `cargo test -p codex-tui
bottom_pane::request_user_input::tests::freeform_ -- --nocapture`

## Notes

- `cargo test -p codex-tui` still reaches an unrelated existing stack
overflow in
`app::tests::discard_side_thread_removes_agent_navigation_entry` on this
checkout.
- `just argument-comment-lint` is locally blocked by Bazel analysis
failing in external `compiler-rt` before the lint runs.
2026-05-19 18:01:38 -03:00
starr-openaiandGitHub 5c43a64e2b Make local environment optional in EnvironmentManager (#23369)
## Summary
- make `EnvironmentManager` local environment/runtime paths optional
- simplify constructor surface around snapshot materialization
- rename local env accessors to `require_local_environment` /
`try_local_environment`

## Validation
- devbox Bazel build for touched crate surfaces
- `//codex-rs/exec-server:exec-server-unit-tests`
- `//codex-rs/app-server-client:app-server-client-unit-tests`
- filtered touched `//codex-rs/core:core-unit-tests` cases
2026-05-19 12:55:34 -07:00
AbhinavandGitHub d661ab70ed Add SubagentStart hook (#22782)
# What

`SubagentStart` runs once when Codex creates a thread-spawned subagent,
before that child sends its first model request. Thread-spawned
subagents use `SubagentStart` instead of the normal root-agent
`SessionStart` hook.

Configured handlers match on the subagent `agent_type`, using the same
value passed to `spawn_agent`. When no agent type is specified, Codex
uses the default agent type.

Hook input includes the normal session-start fields plus:

- `agent_id`: the child thread id.
- `agent_type`: the resolved subagent type.

`SubagentStart` may return `hookSpecificOutput.additionalContext`. That
context is added to the child conversation before the first model
request.

# Lifecycle Scope

Only thread-spawned subagents run `SubagentStart`.

Internal/system subagents such as Review, Compact, MemoryConsolidation,
and Other do not run normal `SessionStart` hooks and do not run
`SubagentStart`. This avoids exposing synthetic matcher labels for
internal implementation paths.

Also the `SessionStart` hook no longer fires for subagents, this matches
behavior with other coding agents' implementation

# Stack

1. This PR: add `SubagentStart`.
2. #22873: add `SubagentStop`.
3. #22882: add subagent identity to normal hook inputs.
2026-05-19 12:45:08 -07:00
Arun EswaraandGitHub d269aa2af9 Harden CLI rate limit window labels (#22929)
## Context

The CLI rate-limit surfaces previously described usage windows as fixed
5-hour and weekly limits. We want the CLI to display whatever supported
rate-limit period the server returns instead of assuming a 5-hour/1-week
pair. This supports generalized Codex rate-limit periods.

## Summary

- Formats CLI rate-limit warning/status labels only for the supported
returned window durations: approximate 5h, daily, weekly, monthly, and
annual.
- Uses generic fallback copy when a primary or secondary window has no
duration, so missing secondary protection data does not produce stale
weekly copy.
- Uses generic fallback copy for unsupported window durations instead of
adding arbitrary hourly, multi-day, multi-week, or multi-year labels.
- Updates status line and terminal title setup descriptions/previews to
talk about primary/secondary usage limits rather than fixed 5h/weekly
limits.
- Adds rendered insta snapshot coverage for the updated rate-limit
status surfaces and `/status` fallback labels.

## Tests
Tested locally:
- one primary window
- one secondary window
- primary and secondary window
2026-05-19 11:22:00 -07:00
viyatb-oaiandGitHub 3c76081876 Make deny canonical for filesystem permission entries (#23493)
## Why
Filesystem permission profiles used `none` for deny-read entries, which
is less direct than the action the entry actually represents. This
change makes `deny` the canonical filesystem permission spelling while
preserving compatibility for older configs that still send `none`.

## What changed
- rename `FileSystemAccessMode::None` to `Deny`
- serialize and generate schemas with `deny` as the canonical value
- retain `none` only as a legacy input alias for temporary config
compatibility
- update filesystem glob diagnostics and regression coverage to use the
canonical spelling
- refresh config and app-server schema fixtures to match the new wire
shape

## Validation
- `cargo test -p codex-protocol`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core config_toml_deserializes_permission_profiles
--lib`
- `cargo test -p codex-core
read_write_glob_patterns_still_reject_non_subpath_globs --lib`

Earlier in the session, a broad `cargo test -p codex-core` run reached
unrelated pre-existing failures in timing/snapshot/git-info tests under
this environment; the targeted surfaces touched by this PR passed
cleanly.
2026-05-19 11:03:47 -07:00
Eric TrautandGitHub ae10708ae0 [2 of 4] tui: route app and skill enablement through app server (#22914)
## Why
App and skill toggles are user config mutations too. When the TUI is
attached to a remote app server, writing those toggles into the local
`config.toml` makes the UI report success without updating the server
that actually owns the session.

This is **[2 of 4]** in a stacked series that moves TUI-owned config
mutations onto app-server APIs.

## What changed
- Routed app enable/disable persistence through app-server config batch
writes.
- Routed skill enable/disable persistence through `skills/config/write`.
- Avoided refreshing local config from disk after these writes when the
TUI is connected to a remote app server.

## Config keys affected
- `apps.<app_id>.enabled`
- `apps.<app_id>.disabled_reason`
- `[[skills.config]]` entries keyed by `path`, with `enabled = false`
used for persisted disables

## Suggested manual validation
- Connect the TUI to a remote app server, disable an app, reconnect, and
confirm the app remains disabled from remote config rather than local
disk state.
- Re-enable the same app and confirm both `apps.<app_id>.enabled` and
`apps.<app_id>.disabled_reason` are cleared remotely.
- Disable a skill in the manage-skills UI and confirm a remote
`[[skills.config]]` disable entry appears.
- Re-enable that skill and confirm the disable entry is removed and the
effective enabled state updates without relying on local config reloads.

## Stack
1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]`
primary settings writes
2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app
and skill enablement
3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]`
feature and memory toggles
4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]`
startup and onboarding bookkeeping
2026-05-19 10:21:07 -07:00