Commit Graph

4968 Commits

Author SHA1 Message Date
Roy Han
faea87d718 comment lint 2026-03-27 14:54:05 -07:00
Roy Han
1ce0530baf fixes 2026-03-27 14:26:15 -07:00
Roy Han
8c2fdee57a rebase 2026-03-27 13:41:33 -07:00
Roy Han
ed327248a2 various fixes 2026-03-27 11:15:51 -07:00
Roy Han
7afe91c1df add client metadata 2026-03-27 11:00:11 -07:00
Roy Han
a1e506f17d rename client to connection 2026-03-27 10:43:05 -07:00
Roy Han
2e725aa97c connection cleanup 2026-03-27 10:38:08 -07:00
Roy Han
4381ebb4f4 lint 2026-03-27 10:35:00 -07:00
Roy Han
b8615d471f shift towards app-server stream consumption 2026-03-27 10:25:52 -07:00
Roy Han
9ae940f704 rebase onto client response changes 2026-03-26 22:38:10 -07:00
Roy Han
36ecc2047a add response to turn facts 2026-03-26 21:59:22 -07:00
rhan-oai
e8be2ec924 Merge branch 'rhan/oob-thread-metadata' into rhan/oob-turn-metadata 2026-03-26 21:34:24 -07:00
rhan-oai
b1ee6e9cec Merge branch 'main' into rhan/oob-thread-metadata 2026-03-26 21:33:51 -07:00
rhan-oai
21a03f1671 [app-server-protocol] introduce generic ClientResponse for app-server-protocol (#15921)
- introduces `ClientResponse` as the symmetrical typed response union to
`ClientRequest` for app-server-protocol
- enables scalable event stream ingestion for use cases such as
analytics
- no runtime behavior changes, protocol/schema plumbing only
2026-03-26 21:33:25 -07:00
Michael Bolin
41fe98b185 fix: increase timeout for rust-ci to 45 minutes for now (#15948)
https://github.com/openai/codex/pull/15478 raised the timeout to 35
minutes for `windows-arm64` only, though I just hit 35 minutes on
https://github.com/openai/codex/actions/runs/23628986591/job/68826740108?pr=15944,
so let's just increase it to 45 minutes. As noted, I'm hoping that we
can bring it back down once we no longer have two copies of the `tui`
crate.
2026-03-26 20:54:55 -07:00
Michael Bolin
be5afc65d3 codex-tools: extract MCP schema adapters (#15928)
## Why

`codex-tools` already owns the shared tool input schema model and parser
from the first extraction step, but `core/src/tools/spec.rs` still owned
the MCP-specific adapter that normalizes `rmcp::model::Tool` schemas and
wraps `structuredContent` into the call result output schema.

Keeping that adapter in `codex-core` means the reusable MCP schema path
is still split across crates, and the unit tests for that logic stay
anchored in `codex-core` even though the runtime orchestration does not
need to move yet.

This change takes the next small step by moving the reusable MCP schema
adapter into `codex-tools` while leaving `ResponsesApiTool` assembly in
`codex-core`.

## What changed

- added `tools/src/mcp_tool.rs` and sibling
`tools/src/mcp_tool_tests.rs`
- introduced `ParsedMcpTool`, `parse_mcp_tool()`, and
`mcp_call_tool_result_output_schema()` in `codex-tools`
- updated `core/src/tools/spec.rs` to consume parsed MCP tool parts from
`codex-tools`
- removed the now-redundant MCP schema unit tests from
`core/src/tools/spec_tests.rs`
- expanded `codex-rs/tools/README.md` to describe this second migration
step

## Test plan

- `cargo test -p codex-tools`
- `cargo test -p codex-core --lib tools::spec::`
2026-03-26 19:57:26 -07:00
Michael Bolin
d838c23867 fix: use matrix.target instead of matrix.os for actions/cache build action (#15933)
This seems like a more precise cache key.
2026-03-27 01:32:13 +00:00
Michael Bolin
d76124d656 fix: make MACOS_DEFAULT_PREFERENCES_POLICY part of MACOS_SEATBELT_BASE_POLICY (#15931) 2026-03-26 18:23:14 -07:00
viyatb-oai
81fa04783a feat(windows-sandbox): add network proxy support (#12220)
## Summary

This PR makes Windows sandbox proxying enforceable by routing proxy-only
runs through the existing `offline` sandbox user and reserving direct
network access for the existing `online` sandbox user.

In brief:

- if a Windows sandbox run should be proxy-enforced, we run it as the
`offline` user
- the `offline` user gets firewall rules that block direct outbound
traffic and only permit the configured localhost proxy path
- if a Windows sandbox run should have true direct network access, we
run it as the `online` user
- no new sandbox identity is introduced

This brings Windows in line with the intended model: proxy use is not
just env-based, it is backed by OS-level egress controls. Windows
already has two sandbox identities:

- `offline`: intended to have no direct network egress
- `online`: intended to have full network access

This PR makes proxy-enforced runs use that model directly.

### Proxy-enforced runs

When proxy enforcement is active:

- the run is assigned to the `offline` identity
- setup extracts the loopback proxy ports from the sandbox env
- Windows setup programs firewall rules for the `offline` user that:
  - block all non-loopback outbound traffic
  - block loopback UDP
  - block loopback TCP except for the configured proxy ports
- optionally allow broader localhost access when `allow_local_binding=1`

So the sandboxed process can only talk to the local proxy. It cannot
open direct outbound sockets or do local UDP-based DNS on its own.The
proxy then performs the real outbound network access outside that
restricted sandbox identity.

### Direct-network runs

When proxy enforcement is not active and full network access is allowed:

- the run is assigned to the `online` identity
- no proxy-only firewall restrictions are applied
- the process gets normal direct network access

### Unelevated vs elevated

The restricted-token / unelevated path cannot enforce per-identity
firewall policy by itself.

So for Windows proxy-enforced runs, we transparently use the logon-user
sandbox path under the hood, even if the caller started from the
unelevated mode. That keeps enforcement real instead of best-effort.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-03-26 17:27:38 -07:00
Michael Bolin
e6e2999209 permissions: remove macOS seatbelt extension profiles (#15918)
## Why

`PermissionProfile` should only describe the per-command permissions we
still want to grant dynamically. Keeping
`MacOsSeatbeltProfileExtensions` in that surface forced extra macOS-only
approval, protocol, schema, and TUI branches for a capability we no
longer want to expose.

## What changed

- Removed the macOS-specific permission-profile types from
`codex-protocol`, the app-server v2 API, and the generated
schema/TypeScript artifacts.
- Deleted the core and sandboxing plumbing that threaded
`MacOsSeatbeltProfileExtensions` through execution requests and seatbelt
construction.
- Simplified macOS seatbelt generation so it always includes the fixed
read-only preferences allowlist instead of carrying a configurable
profile extension.
- Removed the macOS additional-permissions UI/docs/test coverage and
deleted the obsolete macOS permission modules.
- Tightened `request_permissions` intersection handling so explicitly
empty requested read lists are preserved only when that field was
actually granted, avoiding zero-grant responses being stored as active
permissions.
2026-03-26 17:12:45 -07:00
Michael Bolin
44d28f500f codex-tools: extract shared tool schema parsing (#15923)
## Why

`parse_tool_input_schema` and the supporting `JsonSchema` model were
living in `core/src/tools/spec.rs`, but they already serve callers
outside `codex-core`.

Keeping that shared schema parsing logic inside `codex-core` makes the
crate boundary harder to reason about and works against the guidance in
`AGENTS.md` to avoid growing `codex-core` when reusable code can live
elsewhere.

This change takes the first extraction step by moving the schema parsing
primitive into its own crate while keeping the rest of the tool-spec
assembly in `codex-core`.

## What changed

- added a new `codex-tools` crate under `codex-rs/tools`
- moved the shared tool input schema model and sanitizer/parser into
`tools/src/json_schema.rs`
- kept `tools/src/lib.rs` exports-only, with the module-level unit tests
split into `json_schema_tests.rs`
- updated `codex-core` to use `codex-tools::JsonSchema` and re-export
`parse_tool_input_schema`
- updated `codex-app-server` dynamic tool validation to depend on
`codex-tools` directly instead of reaching through `codex-core`
- wired the new crate into the Cargo workspace and Bazel build graph
2026-03-27 00:03:35 +00:00
Son Luong Ngoc
a27cd2d281 bazel: re-organize bazelrc (#15522)
Replaced ci.bazelrc and v8-ci.bazelrc by custom configs inside the main
.bazelrc file. As a result, github workflows setup is simplified down to
a single '--config=<foo>' flag usage.

Moved the build metadata flags to config=ci.
Added custom tags metadata to help differentiate invocations based on
workflow (bazel vs v8) and os (linux/macos/windows).

Enabled users to override the default values in .bazelrc by using a
user.bazelrc file locally.
Added user.bazelrc to gitignore.
2026-03-26 16:50:07 -07:00
Siggi Simonarson
c264c6eef9 Preserve bazel repository cache in github actions (#14495)
Highlights:

- Trimmed down to just the repository cache for faster upload / download
- Made the cache key only include files that affect external
dependencies (since that's what the repository cache caches) -
MODULE.bazel, codex-rs/Cargo.lock, codex-rs/Cargo.toml
- Split the caching action in to explicit restore / save steps (similar
to your rust CI) which allows us to skip uploads on cache hit, and not
fail the build if upload fails

This should get rid of 842 network fetches that are happening on every
Bazel CI run, while also reducing the Github flakiness @bolinfest
reported. Uploading should be faster (since we're not caching many small
files), and will only happen when MODULE.bazel or Cargo.lock /
Cargo.toml files change.

In my testing, it [took 3s to save the repository
cache](https://github.com/siggisim/codex/actions/runs/23014186143/job/66832859781).
2026-03-26 16:41:15 -07:00
viyatb-oai
aea82c63ea fix(network-proxy): fail closed on network-proxy DNS lookup errors (#15909)
## Summary

Fail closed when the network proxy's local/private IP pre-check hits a
DNS lookup error or timeout, instead of treating the hostname as public
and allowing the request.

## Root cause

`host_resolves_to_non_public_ip()` returned `false` on resolver failure,
which created a fail-open path in the `allow_local_binding = false`
boundary. The eventual connect path performs its own DNS resolution
later, so a transient pre-check failure is not evidence that the
destination is public.

## Changes

- Treat DNS lookup errors/timeouts as local/private for blocking
purposes
- Add a regression test for an allowlisted hostname that fails DNS
resolution

## Validation

- `cargo test -p codex-network-proxy`
- `cargo clippy -p codex-network-proxy --all-targets -- -D warnings`
- `just fmt`
- `just argument-comment-lint`
2026-03-26 23:18:04 +00:00
Roy Han
ceff0bdf9a lint 2026-03-26 15:37:06 -07:00
Michael Bolin
5906c6a658 chore: remove skill metadata from command approval payloads (#15906)
## Why

This is effectively a follow-up to
[#15812](https://github.com/openai/codex/pull/15812). That change
removed the special skill-script exec path, but `skill_metadata` was
still being threaded through command-approval payloads even though the
approval flow no longer uses it to render prompts or resolve decisions.

Keeping it around added extra protocol, schema, and client surface area
without changing behavior.

Removing it keeps the command-approval contract smaller and avoids
carrying a dead field through app-server, TUI, and MCP boundaries.

## What changed

- removed `ExecApprovalRequestSkillMetadata` and the corresponding
`skillMetadata` field from core approval events and the v2 app-server
protocol
- removed the generated JSON and TypeScript schema output for that field
- updated app-server, MCP server, TUI, and TUI app-server approval
plumbing to stop forwarding the field
- cleaned up tests that previously constructed or asserted
`skillMetadata`

## Testing

- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-protocol`
- `cargo test -p codex-app-server-test-client`
- `cargo test -p codex-mcp-server`
- `just argument-comment-lint`
2026-03-26 15:32:03 -07:00
viyatb-oai
b52abff279 chore: move bwrap config helpers into dedicated module (#15898)
## Summary
- move the bwrap PATH lookup and warning helpers out of config/mod.rs
- move the related tests into a dedicated bwrap_tests.rs file

## Validation
- git diff --check
- skipped heavier local tests per request

Follow-up to #15791.
2026-03-26 15:15:59 -07:00
Roy Han
29693182c1 rebase 2026-03-26 15:14:18 -07:00
Roy Han
9c504c7f47 fix for tests too 2026-03-26 15:04:00 -07:00
Roy Han
2d6ec4c94b address enum size issue 2026-03-26 15:03:19 -07:00
Roy Han
960bd3e028 normalize event input name 2026-03-26 15:02:53 -07:00
Michael Bolin
609019c6e5 docs: update AGENTS.md to discourage adding code to codex-core (#15910)
## Why

`codex-core` is already the largest crate in `codex-rs`, so defaulting
to it for new functionality makes it harder to keep the workspace
modular. The repo guidance should make it explicit that contributors are
expected to look for an existing non-`codex-core` crate, or introduce a
new crate, before growing `codex-core` further.

## What Changed

- Added a dedicated `The \`codex-core\` crate` section to `AGENTS.md`.
- Documented why `codex-core` should be treated as a last resort for new
functionality.
- Added concrete guidance for both implementation and review: prefer an
existing non-`codex-core` crate when possible, introduce a new workspace
crate when that is the cleaner boundary, and push back on PRs that grow
`codex-core` unnecessarily.
2026-03-26 14:56:43 -07:00
Roy Han
9d034360fa emigrate subagent source logic and tests 2026-03-26 14:53:59 -07:00
Roy Han
c278d0ab61 rebase 2026-03-26 14:52:40 -07:00
Roy Han
9e6d9eb198 resolve borrow error 2026-03-26 14:38:19 -07:00
Roy Han
bb61e20675 introduce generic enum for app-server-protocol constructs 2026-03-26 14:32:55 -07:00
Michael Bolin
dfb36573cd sandboxing: use OsString for SandboxCommand.program (#15897)
## Why

`SandboxCommand.program` represents an executable path, but keeping it
as `String` forced path-backed callers to run `to_string_lossy()` before
the sandbox layer ever touched the command. That loses fidelity earlier
than necessary and adds avoidable conversions in runtimes that already
have a `PathBuf`.

## What changed

- Changed `SandboxCommand.program` to `OsString`.
- Updated `SandboxManager::transform` to keep the program and argv in
`OsString` form until the `SandboxExecRequest` conversion boundary.
- Switched the path-backed `apply_patch` and `js_repl` runtimes to pass
`into_os_string()` instead of `to_string_lossy()`.
- Updated the remaining string-backed builders and tests to match the
new type while preserving the existing Linux helper `arg0` behavior.

## Verification

- `cargo test -p codex-sandboxing`
- `just argument-comment-lint -p codex-core -p codex-sandboxing`
- `cargo test -p codex-core` currently fails in unrelated existing
config tests: `config::tests::approvals_reviewer_*` and
`config::tests::smart_approvals_alias_*`
2026-03-26 20:38:33 +00:00
Michael Bolin
b23789b770 [codex] import token_data from codex-login directly (#15903)
## Why
`token_data` is owned by `codex-login`, but `codex-core` was still
re-exporting it. That let callers pull auth token types through
`codex-core`, which keeps otherwise unrelated crates coupled to
`codex-core` and makes `codex-core` more of a build-graph bottleneck.

## What changed
- remove the `codex-core` re-export of `codex_login::token_data`
- update the remaining `codex-core` internals that used
`crate::token_data` to import `codex_login::token_data` directly
- update downstream callers in `codex-rs/chatgpt`,
`codex-rs/tui_app_server`, `codex-rs/app-server/tests/common`, and
`codex-rs/core/tests` to import `codex_login::token_data` directly
- add explicit `codex-login` workspace dependencies and refresh lock
metadata for crates that now depend on it directly

## Validation
- `cargo test -p codex-chatgpt --locked`
- `just argument-comment-lint`
- `just bazel-lock-update`
- `just bazel-lock-check`

## Notes
- attempted `cargo test -p codex-core --locked` and `cargo test -p
codex-core auth_refresh --locked`, but both ran out of disk while
linking `codex-core` test binaries in the local environment
2026-03-26 13:34:02 -07:00
rreichel3-oai
86764af684 Protect first-time project .codex creation across Linux and macOS sandboxes (#15067)
## Problem

Codex already treated an existing top-level project `./.codex` directory
as protected, but there was a gap on first creation.

If `./.codex` did not exist yet, a turn could create files under it,
such as `./.codex/config.toml`, without going through the same approval
path as later modifications. That meant the initial write could bypass
the intended protection for project-local Codex state.

## What this changes

This PR closes that first-creation gap in the Unix enforcement layers:

- `codex-protocol`
- treat the top-level project `./.codex` path as a protected carveout
even when it does not exist yet
- avoid injecting the default carveout when the user already has an
explicit rule for that exact path
- macOS Seatbelt
- deny writes to both the exact protected path and anything beneath it,
so creating `./.codex` itself is blocked in addition to writes inside it
- Linux bubblewrap
- preserve the same protected-path behavior for first-time creation
under `./.codex`
- tests
- add protocol regressions for missing `./.codex` and explicit-rule
collisions
- add Unix sandbox coverage for blocking first-time `./.codex` creation
  - tighten Seatbelt policy assertions around excluded subpaths

## Scope

This change is intentionally scoped to protecting the top-level project
`.codex` subtree from agent writes.

It does not make `.codex` unreadable, and it does not change the product
behavior around loading project skills from `.codex` when project config
is untrusted.

## Why this shape

The fix is pointed rather than broad:
- it preserves the current model of “project `.codex` is protected from
writes”
- it closes the security-relevant first-write hole
- it avoids folding a larger permissions-model redesign into this PR

## Validation

- `cargo test -p codex-protocol`
- `cargo test -p codex-sandboxing seatbelt`
- `cargo test -p codex-exec --test all
sandbox_blocks_first_time_dot_codex_creation -- --nocapture`

---------

Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-03-26 16:06:53 -04:00
Ruslan Nigmatullin
9736fa5e3d app-server: Split transport module (#15811)
`transport.rs` is getting pretty big, split individual transport
implementations into separate files.
2026-03-26 13:01:35 -07:00
Michael Bolin
b3e069e8cb skills: remove unused skill permission metadata (#15900)
## Why

Skill metadata accepted a `permissions` block and stored the result on
`SkillMetadata`, but that data was never consumed by runtime behavior.
Leaving the dead parsing path in place makes it look like skills can
widen or otherwise influence execution permissions when, in practice,
declared skill permissions are ignored.

This change removes that misleading surface area so the skill metadata
model matches what the system actually uses.

## What changed

- removed `permission_profile` and `managed_network_override` from
`core-skills::SkillMetadata`
- stopped parsing `permissions` from skill metadata in
`core-skills/src/loader.rs`
- deleted the loader tests that only exercised the removed permissions
parsing path
- cleaned up dependent `SkillMetadata` constructors in tests and TUI
code that were only carrying `None` for those fields

## Testing

- `cargo test -p codex-core-skills`
- `cargo test -p codex-tui
submission_prefers_selected_duplicate_skill_path`
- `just argument-comment-lint`
2026-03-26 19:33:23 +00:00
viyatb-oai
b6050b42ae fix: resolve bwrap from trusted PATH entry (#15791)
## Summary
- resolve system bwrap from PATH instead of hardcoding /usr/bin/bwrap
- skip PATH entries that resolve inside the current workspace before
launching the sandbox helper
- keep the vendored bubblewrap fallback when no trusted system bwrap is
found

## Validation
- cargo test -p codex-core bwrap --lib
- cargo test -p codex-linux-sandbox
- just fix -p codex-core
- just fix -p codex-linux-sandbox
- just fmt
- just argument-comment-lint
- cargo clean
2026-03-26 12:13:51 -07:00
Matthew Zeng
3360f128f4 [plugins] Polish tool suggest prompts. (#15891)
- [x] Polish tool suggest prompts to distinguish between missing
connectors and discoverable plugins, and be very precise about the
triggering conditions.
2026-03-26 18:52:59 +00:00
Roy Han
57194fb349 exec underscore 2026-03-26 11:48:08 -07:00
Roy Han
553b92e9e1 test patch for model slug 2026-03-26 11:25:50 -07:00
Roy Han
6d62ce6be2 use anyhow instead of expected 2026-03-26 11:12:06 -07:00
Roy Han
fd58c486dc thread initialize refactoring 2026-03-26 11:09:01 -07:00
Matthew Zeng
25134b592c [mcp] Fix legacy_tools (#15885)
- [x] Fix legacy_tools
2026-03-26 11:08:49 -07:00
Felipe Coury
2c54d4b160 feat(tui): add terminal title support to tui app server (#15860)
## TR;DR

Replicates the `/title` command from `tui` to `tui_app_server`.

## Problem

The classic `tui` crate supports customizing the terminal window/tab
title via `/title`, but the `tui_app_server` crate does not. Users on
the app-server path have no way to configure what their terminal title
shows (project name, status, spinner, thread, etc.), making it harder to
identify Codex sessions across tabs or windows.

## Mental model

The terminal title is a *status surface* -- conceptually parallel to the
footer status line. Both surfaces are configurable lists of items, both
share expensive inputs (git branch lookup, project root discovery), and
both must be refreshed at the same lifecycle points. This change ports
the classic `tui`'s design verbatim:

1. **`terminal_title.rs`** owns the low-level OSC write path and input
sanitization. It strips control characters and bidi/invisible codepoints
before placing untrusted text (model output, thread names, project
paths) inside an escape sequence.

2. **`title_setup.rs`** defines `TerminalTitleItem` (the 8 configurable
items) and `TerminalTitleSetupView` (the interactive picker that wraps
`MultiSelectPicker`).

3. **`status_surfaces.rs`** is the shared refresh pipeline. It parses
both surface configs once per refresh, warns about invalid items once
per session, synchronizes the git-branch cache, then renders each
surface from the same `StatusSurfaceSelections` snapshot.

4. **`chatwidget.rs`** sets `TerminalTitleStatusKind` at each state
transition (Working, Thinking, Undoing, WaitingForBackgroundTerminal)
and calls `refresh_terminal_title()` whenever relevant state changes.

5. **`app.rs`** handles the three setup events (confirm/preview/cancel),
persists config via `ConfigEditsBuilder`, and clears the managed title
on `Drop`.

## Non-goals

- **Restoring the previous terminal title on exit.** There is no
portable way to read the terminal's current title, so `Drop` clears the
managed title rather than restoring it.
- **Sharing code between `tui` and `tui_app_server`.** The
implementation is a parallel copy, matching the existing pattern for the
status-line feature. Extracting a shared crate is future work.

## Tradeoffs

- **Duplicate code across crates.** The three core files
(`terminal_title.rs`, `title_setup.rs`, `status_surfaces.rs`) are
byte-for-byte copies from the classic `tui`. This was chosen for
consistency with the existing status-line port and to avoid coupling the
two crates at the dependency level. Future changes must be applied in
both places.

- **`status_surfaces.rs` is large (~660 lines).** It absorbs logic that
previously lived inline in `chatwidget.rs` (status-line refresh, git
branch management, project root discovery) plus all new terminal-title
logic. This consolidation trades file size for a single place where both
surfaces are coordinated.

- **Spinner scheduling on every refresh.** The terminal title spinner
(when active) schedules a frame every 100ms. This is the same pattern
the status-indicator spinner already uses; the overhead is a timer
registration, not a redraw.

## Architecture

```
/title command
  -> SlashCommand::Title
  -> open_terminal_title_setup()
  -> TerminalTitleSetupView (MultiSelectPicker)
  -> on_change:  AppEvent::TerminalTitleSetupPreview  -> preview_terminal_title()
  -> on_confirm: AppEvent::TerminalTitleSetup         -> ConfigEditsBuilder + setup_terminal_title()
  -> on_cancel:  AppEvent::TerminalTitleSetupCancelled -> cancel_terminal_title_setup()

Runtime title refresh:
  state change (turn start, reasoning, undo, plan update, thread rename, ...)
  -> set terminal_title_status_kind
  -> refresh_terminal_title()
  -> status_surface_selections()  (parse configs, collect invalids)
  -> refresh_terminal_title_from_selections()
     -> terminal_title_value_for_item() for each configured item
     -> assemble title string with separators
     -> skip if identical to last_terminal_title (dedup OSC writes)
     -> set_terminal_title() (sanitize + OSC 0 write)
     -> schedule spinner frame if animating

Widget replacement:
  replace_chat_widget_with_app_server_thread()
  -> transfer last_terminal_title from old widget to new
  -> avoids redundant OSC clear+rewrite on session switch
```

## Observability

- Invalid terminal-title item IDs in config emit a one-per-session
warning via `on_warning()` (gated by
`terminal_title_invalid_items_warned` `AtomicBool`).
- OSC write failures are logged at `tracing::debug` level.
- Config persistence failures are logged at `tracing::error` and
surfaced to the user via `add_error_message()`.

## Tests

- `terminal_title.rs`: 4 unit tests covering sanitization (control
chars, bidi codepoints, truncation) and OSC output format.
- `title_setup.rs`: 3 tests covering setup view snapshot rendering,
parse order preservation, and invalid-ID rejection.
- `chatwidget/tests.rs`: Updated test helpers with new fields; existing
tests continue to pass.

---------

Co-authored-by: Eric Traut <etraut@openai.com>
2026-03-26 11:59:12 -06:00
jif-oai
970386e8b2 fix: root as std agent (#15881) 2026-03-26 18:57:34 +01:00