## Why
`execpolicy` currently keys `prefix_rule()` matching off the literal
first token. That works for rules like `["/usr/bin/git"]`, but it means
shared basename rules such as `["git"]` do not help when a caller passes
an absolute executable path like `/usr/bin/git`.
This PR lays the groundwork for basename-aware matching without changing
existing callers yet. It adds typed host-executable metadata and an
opt-in resolution path in `codex-execpolicy`, so a follow-up PR can
adopt the new behavior in `unix_escalation.rs` and other call sites
without having to redesign the policy layer first.
## What Changed
- added `host_executable(name = ..., paths = [...])` to the execpolicy
parser and validated it with `AbsolutePathBuf`
- stored host executable mappings separately from prefix rules inside
`Policy`
- added `MatchOptions` and opt-in `*_with_options()` APIs that preserve
existing behavior by default
- implemented exact-first matching with optional basename fallback,
gated by `host_executable()` allowlists when present
- normalized executable names for cross-platform matching so Windows
paths like `git.exe` can satisfy `host_executable(name = "git", ...)`
- updated `match` / `not_match` example validation to exercise the
host-executable resolution path instead of only raw prefix-rule matching
- preserved source locations for deferred example-validation errors so
policy load failures still point at the right file and line
- surfaced `resolvedProgram` on `RuleMatch` so callers can tell when a
basename rule matched an absolute executable path
- preserved host executable metadata when requirements policies overlay
file-based policies in `core/src/exec_policy.rs`
- documented the new rule shape and CLI behavior in
`execpolicy/README.md`
## Verification
- `cargo test -p codex-execpolicy`
- added coverage in `execpolicy/tests/basic.rs` for parsing, precedence,
empty allowlists, basename fallback, exact-match precedence, and
host-executable-backed `match` / `not_match` examples
- added a regression test in `core/src/exec_policy.rs` to verify
requirements overlays preserve `host_executable()` metadata
- verified `cargo test -p codex-core --lib`, including source-rendering
coverage for deferred validation errors
The other three Bazel builds have experienced low flakiness in my
experience whereas I find myself re-running the `ubuntu-24.04-arm` jobs
often to shake out the flakes. Disabling for now.
Replay pending client requests after `thread/resume` and emit resolved
notifications when those requests clear so approval/input UI state stays
in sync after reconnects and across subscribed clients.
Affected RPCs:
- `item/commandExecution/requestApproval`
- `item/fileChange/requestApproval`
- `item/tool/requestUserInput`
Motivation:
- Resumed clients need to see pending approval/input requests that were
already outstanding before the reconnect.
- Clients also need an explicit signal when a pending request resolves
or is cleared so stale UI can be removed on turn start, completion, or
interruption.
Implementation notes:
- Use pending client requests from `OutgoingMessageSender` in order to
replay them after `thread/resume` attaches the connection, using
original request ids.
- Emit `serverRequest/resolved` when pending requests are answered
or cleared by lifecycle cleanup.
- Update the app-server protocol schema, generated TypeScript bindings,
and README docs for the replay/resolution flow.
High-level test plan:
- Added automated coverage for replaying pending command execution and
file change approval requests on `thread/resume`.
- Added automated coverage for resolved notifications in command
approval, file change approval, request_user_input, turn start, and turn
interrupt flows.
- Verified schema/docs updates in the relevant protocol and app-server
tests.
Manual testing:
- Tested reconnect/resume with multiple connections.
- Confirmed state stayed in sync between connections.
## Why
CI has been intermittently failing in
`suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch`
because these running-thread resume tests treated `turn/started` as
proof that the thread was already active.
That signal is too early for this path. `turn/started` is emitted
optimistically from
[`turn_start`](1103d0037e/codex-rs/app-server/src/codex_message_processor.rs (L5757-L5767)).
In `single_client_mode`, the listener skips `current_turn_history`
tracking in
[`codex_message_processor.rs`](1103d0037e/codex-rs/app-server/src/codex_message_processor.rs (L6461-L6465)),
so running-thread resume still depends on `ThreadWatchManager` observing
the core `TurnStarted` event in
[`bespoke_event_handling.rs`](1103d0037e/codex-rs/app-server/src/bespoke_event_handling.rs (L152-L156)).
If `thread/resume` lands in that window, the thread can still look
`Idle` and the assertion flakes.
## What
- Add a helper in `codex-rs/app-server/tests/suite/v2/thread_resume.rs`
that waits for `thread/status/changed` to report `Active` for the target
thread.
- Use that public v2 notification as the synchronization barrier in the
four running-thread resume tests instead of relying on `turn/started`.
## Follow-up
This PR keeps the fix at the test layer so we can remove the flake
without changing server behavior. A broader runtime fix should still be
considered separately, for example:
- make `turn/start` eagerly transition the thread to `Active` so
`turn/started` and `thread/status/changed` are coherent
- or revisit the `single_client_mode` guard that skips current-turn
tracking for running-thread resume
## Testing
- `cargo test -p codex-app-server thread_resume -- --nocapture`
- `for i in $(seq 1 10); do cargo test -p codex-app-server
'suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch'
-- --exact --nocapture; done`
## Summary
- Promote ANSI-16 to truecolor for diff rendering when running inside
Windows Terminal
- Respect explicit `FORCE_COLOR` override, skipping promotion when set
- Extract a pure `diff_color_level_for_terminal` function for
testability
- Strip background tints from ANSI-16 diff output, rendering add/delete
lines with foreground color only
- Introduce `RichDiffColorLevel` to type-safely restrict background
fills to truecolor and ansi256
## Problem
Windows Terminal fully supports 24-bit (truecolor) rendering but often
does not provide the usual TERM metadata (`TERM`, `TERM_PROGRAM`,
`COLORTERM`) in `cmd.exe`/PowerShell sessions. In those environments,
`supports-color` can report only ANSI-16 support. The diff renderer
therefore falls back to a 16-color palette, producing washed-out,
hard-to-read diffs.
The screenshots below demonstrate that both PowerShell and cmd.exe don't
set any `*TERM*` environment variables.
| PowerShell | cmd.exe |
|---|---|
| <img width="2032" height="1162" alt="SCR-20260226-nfvy"
src="https://github.com/user-attachments/assets/59e968cc-4add-4c7b-a415-07163297e86a"
/> | <img width="2032" height="1162" alt="SCR-20260226-nfyc"
src="https://github.com/user-attachments/assets/d06b3e39-bf91-4ce3-9705-82bf9563a01b"
/> |
## Mental model
`StdoutColorLevel` (from `supports-color`) is the _detected_ capability.
`DiffColorLevel` is the _intended_ capability for diff rendering. A new
intermediary — `diff_color_level_for_terminal` — maps one to the other
and is the single place where terminal-specific overrides live.
Windows Terminal is detected two independent ways: the `TerminalName`
parsed by `terminal_info()` and the raw presence of `WT_SESSION`. When
`WT_SESSION` is present and `FORCE_COLOR` is not set, we promote
unconditionally to truecolor. When `WT_SESSION` is absent but
`TerminalName::WindowsTerminal` is detected, we promote only the ANSI-16
level (not `Unknown`).
A single override helper — `has_force_color_override()` — checks whether
`FORCE_COLOR` is set. When it is, both the `WT_SESSION` fast-path and
the `TerminalName`-based promotion are suppressed, preserving explicit
user intent.
| PowerShell | cmd.exe | WSL | Bash for Windows |
|---|---|---|---|
|

|

|

|

|
## Non-goals
- This does not change color detection for anything outside the diff
renderer (e.g. the chat widget, markdown rendering).
- This does not add a user-facing config knob; `FORCE_COLOR` already
serves that role.
## Tradeoffs
- The `has_wt_session` signal is intentionally kept separate from
`TerminalName::WindowsTerminal`. `terminal_info()` is derived with
`TERM_PROGRAM` precedence, so it can differ from raw `WT_SESSION`.
- Real-world validation in this issue: in both `cmd.exe` and PowerShell,
`TERM`/`TERM_PROGRAM`/`COLORTERM` were absent, so TERM-based capability
hints were unavailable in those sessions.
- Checking `FORCE_COLOR` for presence rather than parsing its value is a
simplification. In practice `supports-color` has already parsed it, so
our check is a coarse "did the user set _anything_?" gate. The effective
color level still comes from `supports-color`.
- When `WT_SESSION` is present without `FORCE_COLOR`, we promote to
truecolor regardless of `stdout_level` (including `Unknown`). This is
aggressive but correct: `WT_SESSION` is a strong signal that we're in
Windows Terminal.
- ANSI-16 add/delete backgrounds (bright green/red) overpower
syntax-highlighted token colors, making diffs harder to read.
Foreground-only cues (colored text, gutter signs) preserve readability
on low-color terminals.
## Architecture
```
stdout_color_level() ──┐
terminal_info().name ──┤
WT_SESSION presence ──┼──▶ diff_color_level_for_terminal() ──▶ DiffColorLevel
FORCE_COLOR presence ──┘ │
▼
RichDiffColorLevel::from_diff_color_level()
│
┌──────────┴──────────┐
│ Some(TrueColor|256) │ → bg tints
│ None (Ansi16) │ → fg only
└─────────────────────┘
```
`diff_color_level()` is the environment-reading entry point; it gathers
the four runtime signals and delegates to the pure, testable
`diff_color_level_for_terminal()`.
## Observability
No new logs or metrics. Incorrect color selection is immediately visible
as broken diff rendering; the test suite covers the decision matrix
exhaustively.
## Tests
Six new unit tests exercise every branch of
`diff_color_level_for_terminal`:
| Test | Inputs | Expected |
|------|--------|----------|
| `windows_terminal_promotes_ansi16_to_truecolor_for_diffs` | Ansi16 +
WindowsTerminal name | TrueColor |
| `wt_session_promotes_ansi16_to_truecolor_for_diffs` | Ansi16 +
WT_SESSION only | TrueColor |
| `non_windows_terminal_keeps_ansi16_diff_palette` | Ansi16 + WezTerm |
Ansi16 |
| `wt_session_promotes_unknown_color_level_to_truecolor` | Unknown +
WT_SESSION | TrueColor |
| `explicit_force_override_keeps_ansi16_on_windows_terminal` | Ansi16 +
WindowsTerminal + FORCE_COLOR | Ansi16 |
| `explicit_force_override_keeps_ansi256_on_windows_terminal` | Ansi256
+ WT_SESSION + FORCE_COLOR | Ansi256 |
| `ansi16_add_style_uses_foreground_only` | Dark + Ansi16 | fg=Green,
bg=None |
| (and any other new snapshot/assertion tests from commits d757fee and
d7c78b3) | | |
## Test plan
- [x] Verify all new unit tests pass (`cargo test -p codex-tui --lib`)
- [x] On Windows Terminal: confirm diffs render with truecolor
backgrounds
- [x] On Windows Terminal with `FORCE_COLOR` set: confirm promotion is
disabled and output follows the forced `supports-color` level
- [x] On macOS/Linux terminals: confirm no behavior change
Fixes https://github.com/openai/codex/issues/12904
Fixes https://github.com/openai/codex/issues/12890
Fixes https://github.com/openai/codex/issues/12912
Fixes https://github.com/openai/codex/issues/12840
## Why
`PermissionProfile` should describe filesystem roots as absolute paths
at the type level. Using `PathBuf` in `FileSystemPermissions` made the
shared type too permissive and blurred together three different
deserialization cases:
- skill metadata in `agents/openai.yaml`, where relative paths should
resolve against the skill directory
- app-server API payloads, where callers should have to send absolute
paths
- local tool-call payloads for commands like `shell_command` and
`exec_command`, where `additional_permissions.file_system` may
legitimately be relative to the command `workdir`
This change tightens the shared model without regressing the existing
local command flow.
## What Changed
- changed `protocol::models::FileSystemPermissions` and the app-server
`AdditionalFileSystemPermissions` mirror to use `AbsolutePathBuf`
- wrapped skill metadata deserialization in `AbsolutePathBufGuard`, so
relative permission roots in `agents/openai.yaml` resolve against the
containing skill directory
- kept app-server/API deserialization strict, so relative
`additionalPermissions.fileSystem.*` paths are rejected at the boundary
- restored cwd/workdir-relative deserialization for local tool-call
payloads by parsing `shell`, `shell_command`, and `exec_command`
arguments under an `AbsolutePathBufGuard` rooted at the resolved command
working directory
- simplified runtime additional-permission normalization so it only
canonicalizes and deduplicates absolute roots instead of trying to
recover relative ones later
- updated the app-server schema fixtures, `app-server/README.md`, and
the affected transport/TUI tests to match the final behavior