192 Commits

  • path-uri: normalize parent segments in absolute joins (#29903)
    ## Why
    
    `PathUri::join` normalized `..` for relative paths, but its
    absolute-path branch rebuilt URIs through `url::PathSegmentsMut::push`,
    which skips dot segments. `/tmp/a/../b` therefore resolved to `/tmp/a/b`
    instead of `/tmp/b`.
    
    ## What changed
    
    Normalize absolute native path segments before constructing the file
    URI. Parent traversal now clamps at POSIX roots, Windows drive roots,
    and UNC share roots, including paths with repeated separators.
    
    Add platform-independent coverage for POSIX, drive, UNC, root-clamping,
    and repeated-separator cases.
    
    ## Manual validation
    
    - `just test -p codex-utils-path-uri`
  • [codex] fix Windows ConPTY input handling (#29734)
    ## Why
    
    Windows unified-exec TTY input did not behave like the non-Windows PTY
    path. ConPTY sessions could receive the wrong line ending or mishandle
    backspace, especially when sending input to a foreground program through
    PowerShell or cmd. The local, legacy restricted, and elevated paths also
    handled this normalization separately.
    
    ## What changed
    
    - share one stateful Windows TTY input normalizer across local, legacy
    restricted, and elevated runner paths
    - translate LF and split CRLF into one Windows terminal Enter, encode
    backspace as DEL, and preserve UTF-8 and control bytes such as Ctrl-C
    - add Windows integration coverage for Unicode input, backspace, Enter,
    and PowerShell foreground-child Ctrl-C behavior
    
    ## Validation
    
    - `just test -p codex-utils-pty` (13 tests passed; the Unicode
    integration test retried once)
    - the Unicode integration test passed five consecutive runs with retries
    disabled
    - integration coverage sends `cafeé 漢字` through cmd and PowerShell and
    verifies that Ctrl-C interrupts a running PowerShell foreground child
  • Cache plugin namespace during executor skill discovery (#29831)
    ## Why
    
    Executor skill discovery runs before the remote skills catalog is
    available. For a remote environment, each `ExecutorFileSystem` operation
    becomes an exec-server RPC.
    
    Previously, every discovered `SKILL.md` independently resolved its
    plugin namespace by walking its ancestors and probing both supported
    manifest locations. In the common `plugin/skills/<skill>/SKILL.md`
    layout, that repeats 8 RPCs per skill even though every skill under the
    plugin root uses the same namespace. These lookups happen while skills
    are parsed, so their cost grows linearly with the skill count and adds
    directly to first-turn latency.
    
    A selected capability root can also contain standalone skills, multiple
    sibling plugins, nested plugins, or symlinked directories. The
    optimization therefore needs to retain the nearest-ancestor namespace
    for each skill rather than assuming the selected root represents exactly
    one plugin.
    
    ## What changed
    
    - record plugin-root candidates from directory entries already returned
    during skill discovery
    - prune candidates that are not ancestors of any discovered `SKILL.md`
    before reading manifests
    - resolve each relevant plugin root once, with one fallback lookup per
    canonical traversal root for symlinked directories
    - select the nearest cached plugin namespace for each discovered skill
    - avoid namespace lookup entirely when the root contains no skills
    
    No additional directory traversal is required. Namespace work now scales
    with the number of plugin roots that contain discovered skills, rather
    than the total number of skills or unrelated sibling plugins. Standalone
    and nested-plugin names keep their previous behavior.
    
    ## Benchmarks
    
    I used a temporary counting `ExecutorFileSystem` around the real local
    filesystem. Each filesystem operation was counted as one remote RPC and
    given 1 ms of injected latency. Each variant ran three times; times
    below are medians.
    
    ### One plugin with 100 skills
    
    | Operation | Before | After | Delta |
    | --- | ---: | ---: | ---: |
    | `get_metadata` | 1,002 | 303 | -699 |
    | `read_file` | 200 | 101 | -99 |
    | `read_directory` | 102 | 102 | 0 |
    | **Total filesystem RPCs** | **1,304** | **506** | **-798 (-61.2%)** |
    | **Median load time** | **2.890 s** | **0.997 s** | **2.90× faster** |
    
    The namespace-specific work drops from 800 RPCs to 2 in this layout.
    
    ### Multiple plugins under one selected root
    
    These runs compare the correct pre-optimization implementation with the
    final nearest-plugin-root cache. The total plugin skill count stays at
    100 while the number of plugin roots changes.
    
    | Layout | Before RPCs | After RPCs | Reduction | Before | After |
    Speedup |
    | --- | ---: | ---: | ---: | ---: | ---: | ---: |
    | 2 plugins × 50 skills | 1,312 | 530 | 59.6% | 1,819 ms | 711 ms |
    2.56× |
    | 10 plugins × 10 skills | 1,344 | 578 | 57.0% | 1,850 ms | 778 ms |
    2.38× |
    | 50 plugins × 2 skills | 1,504 | 818 | 45.6% | 2,094 ms | 1,086 ms |
    1.93× |
    | 10 plugins × 10 skills + 10 standalone skills | 1,596 | 630 | 60.5% |
    2,209 ms | 860 ms | 2.57× |
    
    The remaining cost grows with the number of relevant plugin manifests.
    Each relevant manifest is read once instead of once per skill, while
    sibling plugins with no discovered skills are not read. Absolute latency
    savings depend on the executor's real RPC latency.
    
    ## Tests
    
    - `just test -p codex-core-skills` (109 passed across the library and
    integration-test binaries)
    - one integration test covers standalone, outer-plugin, nested-plugin,
    and unused sibling-plugin layouts, and asserts the exact set of
    manifests read
  • Load executor skills without host path conversion (#29626)
    ## Why
    
    After #28918, selected skill roots are `PathUri`, but the executor skill
    provider still converts them to the app-server host's `AbsolutePathBuf`.
    A foreign Windows root therefore cannot be discovered by a Unix host,
    and the inverse has the same problem.
    
    This PR keeps executor skill discovery and reads on the filesystem that
    owns the selected root while reusing the existing skill rules.
    
    ## What changed
    
    - Generalize the existing skill traversal to operate on `PathUri`
    through `ExecutorFileSystem`, preserving its depth, directory, symlink,
    and sibling-metadata concurrency behavior.
    - Add a small environment skill loader that reuses the shared discovery,
    frontmatter validation, dependency parsing, product policy, and
    prompt-visibility rules.
    - Keep the environment id and entrypoint `PathUri` in the skill catalog,
    then route `skills.read` back through the same environment filesystem.
    - Preserve the executor's path convention when deriving catalog handles,
    including literal backslashes in POSIX filenames.
    - Resolve plugin namespaces from nearby manifests through URI-native
    filesystem reads.
    - Cover foreign Windows roots, executor-owned reads, namespaces,
    metadata, policy, and path identity.
    
    ```text
    selected root (PathUri)
            |
            v
    shared discovery over ExecutorFileSystem
            |
            v
    environment-bound catalog entry --skills.read--> same ExecutorFileSystem
    ```
    
    No second filesystem abstraction or duplicate traversal implementation
    is introduced.
    
    ## Stack
    
    1. #29614 — add lexical `PathUri` containment.
    2. #29620 — share URI-native manifest path resolution.
    3. #28918 — keep selected plugin roots and resources URI-native.
    4. **This PR** — load executor skills without host path conversion.
    5. #29628 — resolve executor MCP working directories without host path
    conversion.
  • Make selected plugin roots URI-native (#28918)
    ## Why
    
    Selected capability roots belong to the executor filesystem, not the
    app-server host. Converting their path strings into the host's native
    `Path` breaks whenever the two machines use different path conventions,
    such as a Windows executor behind a Unix app-server.
    
    This PR establishes `PathUri` as the selected-plugin boundary so the
    executor remains authoritative for its paths.
    
    ## What changed
    
    - Require `selectedCapabilityRoots[].location.path` to be a canonical
    `file:` URI and deserialize it directly as `PathUri`; native path
    strings are rejected.
    - Update the app-server schema, generated TypeScript, examples, and
    request coverage for the URI contract.
    - Keep selected roots, resolved plugin locations, manifest paths, and
    manifest resources as `PathUri`.
    - Inspect and read plugin roots and manifests only through the selected
    environment's `ExecutorFileSystem`.
    - Parse executor manifests with the shared URI-native parser from #29620
    instead of projecting them onto the host filesystem.
    - Enforce resource containment lexically and preserve the root URI's
    POSIX or Windows path convention.
    - Cover foreign Windows plugin roots and URI-native manifest resources.
    
    ```text
    thread/start
      selectedCapabilityRoots[].location.path = "file:///C:/plugins/demo"
                                  | PathUri
                                  v
                        ExecutorFileSystem
                                  |
                                  +--> plugin.json
                                  +--> manifest resources
    ```
    
    This PR stops at the shared selected-plugin representation. The next two
    PRs remove the remaining host-path projections in the skill and MCP
    consumers.
    
    ## Stack
    
    1. #29614 — add lexical `PathUri` containment.
    2. #29620 — share URI-native manifest path resolution.
    3. **This PR** — keep selected plugin roots and resources URI-native.
    4. #29626 — load executor skills without host path conversion.
    5. #29628 — resolve executor MCP working directories without host path
    conversion.
  • path-uri: remove legacy path deserialization (#29158)
    ## Why
    
    I'd originally added `PathUri` legacy path deserialization thinking we'd
    want it for having `PathUri` in public app-server APIs. Since then we've
    added `LegacyAppPathString` to handle the messy conversions that we need
    for backcompat. It's confusing for `PathUri` to support deserializing
    legacy paths when we don't yet want to actually expose app-server
    callers or rollout storage to the new URI format.
    
    Stacked on top of #29472 to avoid breaking compatibility in case those
    types ended up stored somewhere for someone.
    
    ## What changed
    
    - Parse deserialized `PathUri` values exclusively as valid `file:` URIs.
    - Replace legacy acceptance coverage with rejection coverage for
    top-level filesystem paths and sandbox working directories.
    - Serialize CWDs in hand-built exec-server process requests as `PathUri`
    values.
  • Decouple plugin manifest path resolution (#29620)
    ## Why
    
    Plugin manifests use the same schema whether the package lives on the
    host or in an executor. Only the path representation differs: host
    callers need native `Path` inputs and `AbsolutePathBuf` outputs, while
    executor callers need `PathUri` throughout.
    
    Maintaining separate parsing or resolver implementations would duplicate
    the manifest rules and allow them to drift. This PR instead makes
    URI-native resolution the single parsing path and keeps host conversion
    at the boundary.
    
    ## What changed
    
    - Make `parse_plugin_manifest_uri` the shared manifest parser and
    resolve every path-bearing field as `PathUri`.
    - Keep the existing host entrypoint as a thin adapter: convert its
    native root and manifest path to `PathUri`, run the shared parser, then
    map resources back to `AbsolutePathBuf`.
    - Expose `PluginManifest::try_map_resources` so callers can convert the
    generic resource type without duplicating manifest construction.
    - Resolve relative manifest paths using the root URI's convention:
    backslashes are separators for Windows roots and ordinary filename
    characters for POSIX roots.
    - Apply lexical containment after URI resolution, rejecting absolute
    paths and parent traversal outside the plugin root.
    - Make encoded backslashes fail containment only for Windows URIs;
    encoded `/` remains unsafe for every convention.
    - Use a host-native synthetic root for marketplace fallback manifests so
    the host adapter also works on Windows.
    
    ```text
    host Path --------> PathUri --\
                                  +--> one manifest parser --> PluginManifest<PathUri>
    executor PathUri -------------/
    
    host result: PluginManifest<PathUri> --> PluginManifest<AbsolutePathBuf>
    ```
    
    Existing host manifest behavior is preserved; #28918 is the first
    executor consumer.
    
    ## Verification
    
    - `just test -p codex-utils-path-uri`
    - `just test -p codex-plugin`
    - `just test -p codex-core-plugins`
    
    ## Stack
    
    1. #29614 — add lexical `PathUri` containment.
    2. **This PR** — share URI-native manifest path resolution.
    3. #28918 — keep selected plugin roots and resources URI-native.
    4. #29626 — load executor skills without host path conversion.
    5. #29628 — resolve executor MCP working directories without host path
    conversion.
  • core: resolve view_image paths in selected environment (#29526)
    ## Why
    
    view_image needs to support foreign OS remote executors.
    
    ## What
    
    - resolve image paths against the selected environment as `PathUri` and
    read them through that environment's filesystem
    - keep app-server's public path field wire-compatible as
    `LegacyAppPathString`, with purpose-specific UI rendering
    - cover relative and absolute target-native paths in the core
    integration test and run the full `view_image` suite under wine-exec
    without skips
  • chore(core) rm AskForApproval::OnFailure (#28418)
    ## Summary
    Deletes the OnFailure variant of the `AskForApproval` enum. This option
    has been deprecated since #11631.
    
    ## Testing
    - [x] Tests pass
  • path-uri: add lexical containment (#29614)
    ## Why
    
    Executor-owned paths must stay portable while the orchestrator reasons
    about them. Converting a Windows or remote path to the orchestrator
    host's native path just to check containment breaks that boundary.
    
    ## What changed
    
    - Add lexical containment to `PathUri`.
    - Compare URI authorities and complete path segments, so `plugin-other`
    is not treated as a child of `plugin`.
    - Fail closed for encoded path separators and opaque fallback URIs.
    
    For example:
    
    ```text
    file:///C:/plugins/foo/assets/icon.svg
      is below file:///C:/plugins/foo
    
    file:///C:/plugins/foo2/icon.svg
      is not below file:///C:/plugins/foo
    ```
    
    This is the shared foundation for keeping executor-owned plugin
    resources URI-native without consulting the orchestrator filesystem.
  • mcp: accept foreign absolute cwd for remote stdio (#29493)
    ## Why
    
    Remote stdio MCP servers can run in an environment whose path convention
    differs from the Codex host. A Windows cwd such as
    `C:\Users\openai\share` is absolute for the executor but was rejected by
    a POSIX orchestrator.
    
    Built on #29501, now merged, which only clarifies the host-native
    `PathUri` constructor name.
    
    ## What changed
    
    - Deserialize MCP cwd values as `LegacyAppPathString` so config does not
    apply host path rules.
    - Interpret that spelling as host-native for local launches and convert
    it to `PathUri` at executor launch.
    - Skip host filesystem and command resolution checks for remote stdio in
    `codex doctor`.
    - Add host-independent config and executor-boundary coverage using the
    foreign path convention for each test platform.
    
    ## Validation
    
    - `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
    codex-rmcp-client` (408 passed)
    - `just test -p codex-cli -p codex-rmcp-client` (372 passed)
    - `cargo check --workspace --tests`
    - `just test` (11,311 passed; 43 unrelated environment/timing failures)
    - `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
    codex-mcp-extension -p codex-rmcp-client -p codex-tui`
  • path-uri: clarify host-native path conversion (#29501)
    ## Why
    
    Downstream refactors are producing confusing code with this
    functionality having a very generic name. Encoding the specific
    conversion approach in the method name makes it clearer.
    
    ## What
    
    Rename `PathUri::from_path` to `PathUri::from_host_native_path` and
    update its Rust call sites.
  • core: load AGENTS.md from foreign environments (#28958)
    ## Why
    
    Make it possible to load AGENTS.md from remote exec-servers whose OS is
    different than app-server.
    
    ## What
    
    - keep `AGENTS.md` discovery and provenance as `PathUri`, with
    root-aware parent and ancestor traversal
    - expose lifecycle instruction sources as legacy app-server path strings
    in events while retaining `PathUri` internally
    - preserve and test mixed POSIX and Windows paths in model context and
    TUI status output
    - cover remote Windows loading end to end by seeding the Wine prefix
    through host filesystem APIs
    - fix bug in `PathUri`'s parent() implementation that would erase
    Windows drive letters
  • [codex] Remove hardcoded app ID filters (#28947)
    ## Summary
    
    - remove the duplicated originator-specific connector ID denylists
    - stop filtering connector directory/accessibility results and
    live/cached Codex Apps MCP tools by hardcoded connector ID
    - remove the now-unused `codex-login` dependency from
    `codex-utils-plugins`
    - update regression coverage so formerly blocked connector IDs are
    preserved
    
    ## Why
    
    The client-side policy was duplicated across crates, used opaque IDs
    without ownership or expiry information, and could drift between app
    listing and MCP tool behavior. Server-provided visibility,
    authorization, plugin discoverability, accessibility, enabled-state
    handling, and consequential-tool approval templates remain unchanged.
    
    ## Validation
    
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `git diff --check`
    - confirmed the final diff contains no hardcoded denylist symbols
    
    A targeted `codex-mcp` test build spent an unusually long time in local
    compilation/linking. Its first attempt exposed a test-only `PartialEq`
    assertion issue, which was corrected. A follow-up non-linking `cargo
    check -p codex-mcp --tests` was still running when this draft was
    opened; CI should provide the complete Rust validation.
  • apply-patch: carry paths as PathUri (#28854)
    ## Why
    
    Allows the model to edit files that are hosted on a different OS than
    where app-server is running.
    
    ## What
    
    * Use `PathUri` for apply_patch-internal data structures
    * Limit `PathUri` -> `AbsolutePathBuf` conversion to cases where the
    inferred path convention matches the host OS, allows requiring valid
    paths to pass to perms check
    * Adds `PathConvention::path_segments()` for iterating over path
    segments regardless of OS
    * Handle cross-platform relative paths in path filename parsing for
    sniffing a shell
    * Ensure we can apply patches in the wine e2e test
  • [codex] Pass plugin namespace into skill loading (#28608)
    ## What changed
    
    - retain the parsed plugin manifest namespace on loaded plugins
    - carry that namespace through `PluginSkillRoot` and `SkillRoot`
    - use the provided namespace when qualifying plugin skill names
    - include the namespace in the skills cache key
    
    ## Why
    
    Plugin loading has already parsed `plugin.json`, but skill parsing
    currently walks every `SKILL.md` ancestor and probes/reads the manifest
    again to reconstruct the same namespace. Passing the parsed namespace
    removes those repeated filesystem calls, which are particularly costly
    on remote filesystems.
    
    Context:
    https://openai.slack.com/archives/C0ARA9GF5D4/p1781639496496439?thread_ts=1781202444.891669&cid=C0ARA9GF5D4
    
    ## Impact
    
    Plugin skill names remain unchanged. A regression test uses a
    deliberately different on-disk manifest name to verify that plugin roots
    use the provided parsed namespace.
    
    ## Validation
    
    - `just test -p codex-core-skills -p codex-core-plugins -p codex-plugin
    -p codex-utils-plugins` (352 passed)
    - `just fix -p codex-core-skills -p codex-core-plugins -p codex-plugin
    -p codex-utils-plugins`
    - `just fmt`
  • unified-exec: retain PathUri in command events (#28780)
    ## Why
    
    App-server must report command events containing foreign-platform paths
    without changing existing client or rollout path-string formats.
    
    ## What changed
    
    - retain `PathUri` through exec command begin/end events
    - convert cwd values to `LegacyAppPathString` at the app-server
    compatibility boundary
    - drop command actions with foreign paths and log them
    - serialize rollout-trace cwd values using their inferred native path
    representation
    - restore Wine coverage for retained Windows cwd values and successful
    completion
  • path-uri: decouple native path parsing (#28778)
    ## Why
    
    `PathUri::join` should not depend on the app-server compatibility
    wrapper `LegacyAppPathString` to parse native paths. Native path parsing
    belongs to the URI abstraction that it constructs.
    
    ## What
    
    Move platform-independent native path parsing into the root `PathUri`
    module. `PathUri::join` and `LegacyAppPathString` now share the
    crate-private `PathUri::from_absolute_native_path` constructor.
  • unified-exec: preserve PathUri through exec-server (#28681)
    ## Why
    
    It should be possible for app-server to handle "foreign" OS paths in
    unified_exec working directories, allowing e.g. a Linux app-server to
    run processes on e.g. a Windows exec-server.
    
    ## What
    
    Convert the core unified_exec cwd values to use `PathUri`.
    
    Adds fallible path conversion in several places to try to minimize the
    scope of this change. The only time this change suppresses errors from
    converting `PathUri` to an `AbsolutePathBuf` is when the turn is
    configured with no sandboxing at all to allow us to make progress
    testing without sandboxing.
    
    Future changes to apply_patch and sandboxing will clean up these error
    paths.
    
    A tool's cwd is resolved from joining a model-provided workdir to the
    environment's cwd. When using `AbsolutePathBuf::join()`, an
    absolute-path workdir would overwrite the environment's cwd and we would
    resolve permissions/sandboxing against the model-provided path. This
    change extends `PathUri::join()` to also treat an absolute rhs as an
    override of the base/lhs.
    
    This also removes some coverage from the remove_env_windows tests until
    a follow-up converts foreign paths in command exec events correctly.
    
    ## Breaking Changes
    
    When using `AbsolutePathBuf::join()` for workdir resolution, we ended up
    resolving tilde-prefixed paths against the app-server's `$HOME`, e.g.
    `~/foo/bar` becomes `/home/anp/foo/bar`. It's difficult to do this with
    `PathUri` joining, so after offline discussion this PR no longer
    implements it.
    
    A quick check of some power users' rollouts suggests that models don't
    actually generate home-prefixed absolute working directories for their
    spawns, so this shouldn't have any real blast radius.
  • core: render remote environment cwd natively (#28152)
    ## Why
    
    Model-visible `<environment_context>` should match the environment of
    the executor, not of the app server.
    
    Stacked on #28146.
    
    ## What
    
    - Keep selected environment cwd values as `PathUri` while building
    environment context.
    - Render cwd text using the path convention represented by the URI, with
    the canonical URI as a fallback.
    - Preserve compatibility with legacy `TurnContextItem.cwd` values when
    reconstructing and diffing context.
    - Extend the Wine-backed remote Windows test to assert that the model
    sees `powershell` and `C:\windows`.
  • Clarify model-generated and legacy app path types (#28577)
    ## Why
    
    `ApiPathString` kind of implies that it can be used anywhere we pull a
    path out of JSON, but it's not really appropriate for tool arguments
    when the model might generate relative paths.
    
    Prefer `String` for model-generated paths and we can handle the
    conversion per feature for now and define a shared abstraction later if
    it makes sense.
    
    # What
    
    Rename `ApiPathString` to `AppLegacyPathString` to clarify its role.
    
    Expand the `path-types` skill to tell the model to leave tool args as
    bare strings.
  • [codex] Warn clearly when code mode output is truncated (#28467)
    ## Summary
    
    - make `formatted_truncate_text` prepend `Warning: truncated output
    (original token count: N)` above the existing `Total output lines`
    header
    - update direct formatter, unified-exec, user-shell, and code-mode
    expectations
    - add core unit coverage that runs in Bazel without requiring the
    skipped V8-backed code-mode integration suite
    
    ## Validation
    
    - `cargo test -p codex-utils-output-truncation -- --nocapture` (17
    passed)
    - `cargo test -p codex-core --lib
    truncated_text_output_starts_with_warning -- --nocapture`
    - `cargo test -p codex-core --test all
    clamps_model_requested_max_output_tokens_to_policy -- --nocapture` (2
    passed)
    - `cargo test -p codex-core --test all
    unified_exec_formats_large_output_summary -- --nocapture`
    - `cargo test -p codex-core --test all
    user_shell_command_output_is_truncated_in_history -- --nocapture`
    - Bazel CI exercises the shared formatter and downstream integration
    expectations
  • path-uri: clarify invalid host path errors (#28473)
    ## Why
    
    Ensure a consistent string format when exposing path conversion errors
    to the model.
    
    ## What
    
    - Render `PathUriParseError::InvalidFileUriPath` as `'$PATH' is invalid
    on '$OS'`.
  • Use ApiPathString in app-server filesystem permission paths (#28367)
    ## Why
    
    Clients running an app-server on one OS and an exec-server on another OS
    need to be able to pass sandbox config to app-server that refers to
    resources on the executor's foreign OS.
    
    ## What
    
    `AbsolutePathBuf` can't represent these paths and we don't want users to
    be exposed to `PathUri` yet, so this moves the public app-server API to
    be expressed in terms of `ApiPathString`.
    
    Stacked on #28165.
    
    - change app-server v2 filesystem permission paths, including legacy
    read/write roots, to `ApiPathString`
    - localize API paths through `PathUri` when converting into the current
    native core permission types
    - make path-bearing permission conversions fallible and surface
    localization failures instead of silently treating malformed grants as
    ordinary denials
    - propagate conversion failures through app-server and TUI approval
    handling
    - regenerate the app-server JSON and TypeScript schemas
    - leave migration TODOs on native-path conversions so they can be
    removed once core permission paths use `PathUri`
  • Use aws-lc-rs for rustls crypto provider (#27706)
    ## Why
    
    Some enterprise TLS proxies issue certificate chains signed with
    `ecdsa_secp521r1_sha512` / `ECDSA_NISTP521_SHA512`. Custom CA
    configuration such as `SSL_CERT_FILE` can add the right trust root, but
    it cannot make `rustls`'s `ring` verifier support a certificate
    signature algorithm it does not advertise.
    
    That can still break TLS after the CA bundle is configured, including on
    Rust websocket paths that call the shared
    `ensure_rustls_crypto_provider()` helper, such as the Responses
    websocket connector and remote app-server client:
    
    -
    [`codex-api/src/endpoint/responses_websocket.rs`](https://github.com/openai/codex/blob/eddc5c75ed527a8348bfcaa85692e53189600833/codex-rs/codex-api/src/endpoint/responses_websocket.rs#L441)
    -
    [`app-server-client/src/remote.rs`](https://github.com/openai/codex/blob/eddc5c75ed527a8348bfcaa85692e53189600833/codex-rs/app-server-client/src/remote.rs#L718)
    
    The `aws-lc-rs` `rustls` provider supports this P-521/SHA-512
    certificate signature scheme, so use it as Codex's process-wide `rustls`
    provider.
    
    ## What Changed
    
    - Switch the workspace `rustls` feature from `ring` to `aws_lc_rs`.
    - Update `codex-utils-rustls-provider` to install
    `rustls::crypto::aws_lc_rs::default_provider()`.
    - Add an assertion and integration test that the installed provider
    supports `ECDSA_NISTP521_SHA512`.
    
    ## Verification
    
    ```shell
    just fmt
    just test -p codex-utils-rustls-provider
    just bazel-lock-update
    just bazel-lock-check
    ```
  • bound prompt image cache retention (#28294)
    ## Why
    
    The prompt image cache was bounded to 32 entries, but not by the size of
    those entries. A set of large encoded images could therefore retain
    substantially more memory than intended. Cache hits also cloned the full
    encoded payload.
    
    ## What changed
    
    - cap the cache at 64 MiB of encoded image data while preserving its
    existing 32-entry limit
    - skip caching an image that exceeds the entire byte budget
    - evict least-recently-used entries until the cache is back within its
    byte budget
    - share cached encoded bytes with `Arc<[u8]>` so cache hits do not
    deep-clone image payloads
    
    ## Validation
    
    - `just test -p codex-utils-image`
  • path-uri: render native paths across platforms (#27819)
    ## Why
    
    We're moving to `PathUri` in more places to support cross-OS
    app-server/exec-server, but we don't want to expose the URI encoding to
    users of app-server's public APIs yet.
    
    We'll need to translate at the app-server API boundary between
    client-visible "regular" paths that are appropriate for the OS of the
    environment for which the paths make sense, which means using the
    environment's path personality to do the conversion.
    
    `PathUri` doesn't yet attempt to encode environment ID, so for now we'll
    sniff the most likely path convention for a given path.
    
    ## What
    
    - Add `PathConvention` and `NativePathString` with host-independent
    POSIX, Windows drive, and UNC rendering.
    - Cover cross-host rendering, encoding, Unicode, invalid components.
  • build: run buildifier from just fmt (#28125)
    ## Intent
    
    Keep Bazel and Starlark files consistently formatted without requiring
    contributors to install or version buildifier themselves.
    
    ## Implementation
    
    - Add a SHA-256-pinned, cross-platform DotSlash manifest for buildifier
    v8.5.1.
    - Run buildifier from the shared `just fmt` and `just fmt-check` driver,
    with Windows-safe explicit DotSlash invocation.
    - Provision DotSlash in formatting CI and contributor devcontainers, and
    document the source-build prerequisite.
    - Apply the initial mechanical buildifier formatting baseline.
  • [codex] make PathUri::from_abs_path infallible (#27976)
    ## Why
    
    `PathUri::from_abs_path` can fail for absolute paths that do not have a
    normal `file:` URI representation, forcing filesystem call sites to
    handle a conversion error even though the original path can be preserved
    losslessly.
    
    ## What
    
    Make `from_abs_path` infallible and migrate its callers. Unrepresentable
    paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or
    Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The
    leading encoded null reserves a namespace that cannot collide with a
    real Unix or Windows path, and fallback URIs remain opaque to lexical
    path operations.
    
    ## Validation
    
    Added path-URI coverage for Unix null and non-UTF-8 paths, Windows
    device/verbatim and non-Unicode paths, serialization, malformed
    fallbacks, opaque lexical operations, invalid native payloads, and
    literal `/bad/path` collision resistance.
  • Add executor-owned plugin resolution (#27692)
    ## Why
    
    CCA can select a capability root that lives in an executor environment,
    but
    Codex only had a host-filesystem plugin loader. Before selected executor
    plugins can contribute MCP servers, we need a small package boundary
    that can
    answer:
    
    > Does this selected root contain a plugin, and if so, what does its
    manifest
    > declare?
    
    The answer must come from the selected environment's filesystem. A
    failed
    executor lookup must never fall back to the orchestrator filesystem.
    
    ## What this changes
    
    This PR introduces:
    
    ```rust
    PluginProvider::resolve(root)
        -> Result<Option<ResolvedPlugin>, Error>
    ```
    
    `ExecutorPluginProvider` resolves one `SelectedCapabilityRoot` through
    its
    exact `environment_id`. It checks the recognized manifest locations,
    reads the
    manifest through that environment's `ExecutorFileSystem`, and returns an
    inert
    `ResolvedPlugin` containing:
    
    - the opaque selected-root ID;
    - the environment-bound plugin root;
    - the authority-bound manifest resource;
    - parsed metadata and authority-bound component locators.
    
    Descriptor construction rejects manifest or component paths outside the
    selected package root, so consumers cannot accidentally lose the package
    boundary when they receive a resolved plugin.
    
    If the root has no plugin manifest, resolution returns `None`, allowing
    the
    caller to treat it as a standalone capability such as a skill.
    
    ```text
    selected root: repo -> env-1:/workspace/repo
                             |
                             | env-1 filesystem only
                             v
                 .codex-plugin/plugin.json
                             |
                             v
            ResolvedPlugin { authority, root, manifest }
    ```
    
    The existing host loader and the new executor provider now share the
    same
    manifest parser. Existing `codex-core-plugins::manifest` type paths
    remain
    available through re-exports, so host behavior and callers are
    unchanged.
    
    ## Scope
    
    This is intentionally a non-user-visible package-resolution PR. It does
    not:
    
    - parse or register plugin MCP server configurations;
    - activate skills, connectors, hooks, or MCP servers;
    - change app-server wiring;
    - introduce host fallback, caching, or lifecycle behavior.
    
    #27670 has merged, and this PR is now based directly on `main`. Together
    with
    the resolved MCP catalog from #27634, it establishes the inputs needed
    for the
    executor stdio MCP vertical without changing the existing MCP runtime.
    
    ## Follow-up
    
    The next PR will consume `ResolvedPlugin`, read its declared/default MCP
    config
    through the same executor filesystem, bind supported stdio servers to
    that
    environment, and feed those registrations into the resolved MCP catalog.
    An
    app-server E2E will prove that selecting an executor plugin exposes and
    invokes
    its tool on the owning executor.
    
    Resume/fork semantics, dynamic environment replacement, and non-stdio
    placement remain separate lifecycle decisions.
    
    ## Validation
    
    - `just fmt`
    - `cargo check --tests -p codex-plugin -p codex-core-plugins`
    - `just bazel-lock-check`
    - `git diff --check`
    
    Test targets were compiled but not executed locally; CI will run the
    test and
    Clippy suites.
  • [codex] Remove async_trait from first-party code (#27475)
    ## Why
    
    First-party async traits should expose their `Send` contracts explicitly
    without requiring `async_trait`. This completes the migration pattern
    established in #27303 and #27304.
    
    ## What changed
    
    - Replaced the remaining first-party `async_trait` traits with native
    return-position `impl Future + Send` where statically dispatched and
    explicit boxed `Send` futures where object safety is required.
    - Kept implementations behavior-preserving, outlining existing async
    bodies into inherent methods where that keeps the diff reviewable.
    - Removed all direct first-party `async-trait` dependencies and the
    workspace dependency declaration.
    - Added a cargo-deny policy that permits `async-trait` only through the
    remaining transitive wrapper crates.
    - Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
    keep the full cargo-deny check passing.
    
    ## Validation
    
    - `just test -p codex-exec-server`: 216 passed, 2 skipped.
    - `just test -p codex-model-provider`: 39 passed.
    - `just test -p codex-core` and `just test`: changed tests passed;
    remaining failures are environment-sensitive suites unrelated to this
    migration.
    - `cargo deny check`
    - `just fix`
    - `just fmt`
    - `cargo shear`
    - `just bazel-lock-check`
  • [codex] migrate ExecutorFileSystem paths to PathUri (#27424)
    ## Why
    
    We're moving exec-server to use PathUri for its internal path
    representations.
    
    ## What
    
    Move `ExecutorFileSystem` APIs to use `PathUri` instead of
    `AbsolutePathBuf`. Future changes will convert higher-level parts of
    exec-server.
  • image: preserve metadata when resizing prompt images (#27266)
    ## Summary
    
    - Preserve ICC profiles and EXIF metadata when resizing and re-encoding
    prompt images.
    - Retain EXIF orientation metadata without rotating or otherwise
    modifying the pixel data locally.
    - Support metadata preservation for PNG, JPEG, and WebP outputs.
    - Continue returning the original bytes when an image does not require
    re-encoding.
    
    This intentionally preserves the metadata most important for rendering
    prompt images faithfully. Other format-specific metadata is not copied.
    
    ## Motivation
    
    Client-side resizing previously discarded image metadata during
    re-encoding. This could lose color-profile information and EXIF
    orientation needed by downstream image consumers.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/27245
    -  `2` https://github.com/openai/codex/pull/27247
    -  `3` https://github.com/openai/codex/pull/27246
    - 👉 `4` https://github.com/openai/codex/pull/27266
  • core: resize all history images behind a feature flag (#27247)
    ## Summary
    
    Adds complete client-side image preparation behind the default-off
    `resize_all_images` feature flag.
    
    When enabled, local image producers defer decoding and resizing. Images
    are prepared centrally before insertion into conversation history,
    covering user input, `view_image`, and structured tool-output images.
    
    ## Behavior
    
    - Processes base64 `data:` images in messages and function/custom tool
    outputs.
    - Leaves non-data URLs, including HTTP(S) URLs, unchanged.
    - Applies image-detail budgets:
      - `high` and omitted: 2048px maximum dimension and 2.5K 32px patches.
      - `original`: 6000px maximum dimension and 10K 32px patches.
      - `auto`: uses the same 2048px / 2.5K-patch budget as high.
      - `low`: unsupported and replaced with an actionable placeholder.
    - Preserves original image bytes when no resize or format conversion is
    needed.
    - Enforces the shared 1 GiB encoded and decoded data-URL sanity limits.
    - Replaces only an image that fails preparation, preserving sibling
    content and tool-output metadata.
    - Uses bounded placeholders distinguishing generic processing failures,
    oversized images, and unsupported `low` detail.
    - Prepares resumed and forked history before installing it as live
    history without modifying persisted rollouts.
    
    ## Flag-Off Behavior
    
    When `resize_all_images` is disabled:
    
    - Existing local user-input and `view_image` processing remains
    unchanged.
    - Existing decoding and error behavior remains unchanged.
    - Arbitrary tool-output images are not processed.
    - HTTP(S) image URLs continue to be forwarded unchanged.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/27245
    - 👉 `2` https://github.com/openai/codex/pull/27247
    -  `3` https://github.com/openai/codex/pull/27246
    -  `4` https://github.com/openai/codex/pull/27266
  • image: add shared data URL preparation utilities (#27245)
    ## Summary
    
    Add shared image-processing primitives needed for centralized image
    preparation in a follow-up PR.
    
    - Add `load_data_url_for_prompt` for decoding and preparing base64 image
    data URLs.
    - Add configurable maximum-dimension and 32px patch-budget resizing.
    - Enforce a 1 GiB sanity limit on both encoded and decoded data-URL
    representations.
    - Preserve original PNG, JPEG, and WebP bytes when resizing is
    unnecessary.
    - Preserve the existing GIF-to-PNG behavior.
    - Move image utility tests into the existing sidecar test module.
    
    ## Behavior
    
    This PR is intended to be runtime behavior-preserving.
    
    Existing production callers continue using
    `PromptImageMode::ResizeToFit` and `PromptImageMode::Original` with
    their existing semantics. The new data-URL entrypoint and configurable
    resize mode have no production callers in this PR; they are used by the
    next PR in the stack.
    
    This PR does not change user-input handling, `view_image`, history
    insertion, request construction, HTTP image URL forwarding, or
    app-server behavior.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    - 👉 `1` https://github.com/openai/codex/pull/27245
    -  `2` https://github.com/openai/codex/pull/27247
    -  `3` https://github.com/openai/codex/pull/27246
    -  `4` https://github.com/openai/codex/pull/27266
  • [codex] add io PathUri native conversion APIs (#27280)
    ## Why
    
    Discovered some rough edges in the API while making use of it more
    widely within exec-server. It would be a lot more convenient for
    existing users of `AbsolutePathBuf` if `PathUri` conversion methods
    returned `std::io::Result`s.
    
    ## What
    
    * `PathUri::to_native_path()` -> `PathUri::to_abs_path()`
    * `PathUri::from_file_path()` -> `PathUri::from_abs_path()`
  • [codex] Handle Ctrl-C for non-TTY unified exec (#26734)
    ## Why
    
    A long-running unified exec process started with `tty: false` could not
    be interrupted via `write_stdin`: ordinary non-TTY stdin writes are
    rejected once stdin is closed, but an exact U+0003 payload should still
    map to a process interrupt. The interrupt should flow through the same
    process lifecycle path as a real signal so Codex preserves
    process-reported output and exit metadata instead of fabricating a
    Ctrl-C exit code or tearing down the session early.
    
    ## What Changed
    
    - Add `process/signal` to exec-server with `ProcessSignal::Interrupt`
    and an empty response.
    - Add a non-consuming `ProcessHandle::signal` path for spawned
    processes; on Unix it sends SIGINT to the process group and leaves
    terminate/hard-kill unchanged.
    - Route non-TTY U+0003 `write_stdin` through `process.signal(...)`
    instead of `terminate`, then let the normal post-write collection path
    drain output and observe exit.
    - Add exec-server coverage where a shell `trap INT` handler prints the
    signal and exits with its own code.
    - Add unified exec coverage where a `tty: false` process traps SIGINT,
    emits output, and exits with its own code.
    
    ## Validation
    
    - `just test -p codex-exec-server
    exec_process_signal_interrupts_process`
    - `just test -p codex-exec-server`
    - `just test -p codex-core
    write_stdin_ctrl_c_interrupts_non_tty_session`
  • Add typed file URIs (#26840)
    ## Why
    
    Codex needs stable `file:` URI identifiers that can cross process and
    operating-system boundaries without eagerly interpreting them as native
    paths. Existing fields also need to keep accepting absolute path strings
    during migration.
    
    ## What changed
    
    - Add `codex-utils-path-uri` with a validated, immutable `PathUri`
    wrapper that currently accepts only `file:` URLs.
    - Expose URI-level `basename`, `parent`, and `join` operations that
    preserve authorities and percent encoding without guessing the source
    operating system.
    - Keep native conversion explicit through `AbsolutePathBuf` and the
    current host rules.
    - Serialize as canonical URI text while accepting both URI text and
    legacy absolute native paths during deserialization.
    - Add adversarial coverage for Windows-looking and POSIX paths, UNC
    authorities, encoded metadata characters, non-UTF-8 POSIX paths, URI
    hierarchy operations, and legacy serde round trips.
  • [codex] Support model-defined reasoning efforts (#26444)
    ## Summary
    - accept non-empty model-defined reasoning effort values while
    preserving built-in effort behavior
    - propagate the non-Copy effort type through core, app-server, TUI,
    telemetry, and persistence call sites
    - preserve string wire encoding and expose an open-string schema for
    clients
    - update model selection and shortcut behavior for model-advertised
    effort values
    
    ## Root cause
    `ReasoningEffort` gained a string-backed custom variant, so it could no
    longer implement `Copy` or rely on derived closed-enum serialization.
    Existing consumers still moved effort values from shared references and
    assumed a fixed built-in value set.
    
    ## Validation
    - `just fmt`
    - Local tests and compilation were not run per request; relying on CI.
  • fix(linux-sandbox): preserve shell cleanup on interruption (#22729)
    ## Why
    Interrupted `shell_command` calls can race with the outer tool-dispatch
    cancellation path. When that happens, the runtime future may be dropped
    before the spawned process gets a chance to run `SIGTERM` cleanup. For
    bwrapd-backed Linux sandbox commands, that can leave synthetic
    protected-path mount bookkeeping such as `.git/.codex` registrations
    under `/tmp` behind after a TUI interruption.
    
    The relevant cancellation points are the outer dispatch race in
    [`core/src/tools/parallel.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/parallel.rs#L91-L132)
    and the process shutdown logic in
    [`core/src/exec.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/exec.rs#L1367-L1393).
    
    ## What changed
    - Keep `shell_command` dispatch alive long enough for the runtime to
    finish cancellation cleanup instead of immediately returning the
    synthetic aborted response.
    - Fold shell-turn cancellation into the existing `ExecExpiration` path
    in
    [`core/src/tools/runtimes/shell.rs`](https://github.com/openai/codex/blob/bd184ba84703cc924921ed883f0cf17d3dba60ff/codex-rs/core/src/tools/runtimes/shell.rs#L267-L274),
    so cancellation and timeout behavior stay centralized.
    - On cancellation, send `SIGTERM` first, wait briefly for cleanup to
    run, then hard-kill any remaining descendants in the original process
    group.
    - Treat `ESRCH` as an already-gone process-group cleanup case in
    `codex-utils-pty`, which keeps best-effort teardown from surfacing a
    stale-process race as an error.
    
    ## Verification
    - `cargo test -p codex-core cancellation`
    - Added regression coverage for:
      - `shell_tool_cancellation_waits_for_runtime_cleanup`
      - `process_exec_tool_call_cancellation_allows_sigterm_cleanup`
  • Uprev Rust toolchain pins to 1.95.0 (#24684)
    ## Summary
    - Bump the workspace Rust toolchain from `1.93.0` to `1.95.0` across
    Cargo, Bazel, CI, release workflows, devcontainers, and the Codex
    environment config.
    - Refresh `MODULE.bazel.lock` so the Bazel Rust toolchain artifacts
    match the new version.
    - Leave purpose-specific toolchains unchanged, including the
    `argument-comment-lint` nightly and the upstream `rusty_v8` `1.91.0`
    build pin.
    - Includes fixes for new lints from `just fix` and a few codex-authored
    fixes for lints without a suggestion.
  • [codex] Add image re-encoding benchmarks (#23935)
    ## Summary
    - add Divan benchmarks for prompt image re-encoding paths
    - wire the image benchmark smoke test into Rust CI workflows
    
    ## Why
    Image prompt handling includes re-encoding work that benefits from
    repeatable benchmark coverage so changes can be measured in CI and
    locally.
    
    This already helped identify a potential regression from changing compiler flags.
    
    ## Impact
    Developers can run and compare the new image re-encoding benchmarks, and
    CI exercises the benchmark target via the Rust benchmark smoke test.
  • Prefer just test over cargo test in docs (#23910)
    `cargo test` for the core and other crates fails on a fresh macOS
    checkout without the right stack size variable. This change encourages
    using the just test command that sets the environment up correctly.
    
    As a bonus, this should encourage agents to get more benefit out of
    nextest's parallel execution.
  • fix: Allow plugin skills to share plugin-level icon assets (#23776)
    Thread the plugin root through plugin skill loading so skill interface
    icons can reference shared plugin assets, such as ../../assets/logo.svg.
  • 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).
  • cli: rename profile v2 flag to --profile (#23883)
    ## Why
    
    Profile v2 is taking over the user-facing profile selection path, so the
    CLI no longer needs to expose the transitional `--profile-v2` spelling.
    This switches the public args surface to `--profile` before the
    remaining legacy profile plumbing is removed separately.
    
    ## What
    
    - Rebind `--profile` and `-p` to the v2 profile name argument that
    selects `$CODEX_HOME/<name>.config.toml`.
    - Stop parsing the legacy shared CLI profile argument while keeping its
    implementation path in place for follow-up cleanup.
    - Update CLI validation, profile-name parse errors, and the
    legacy-profile collision message/tests to refer to `--profile`.
    
    ## Testing
    
    - `cargo test -p codex-cli -p codex-config -p codex-protocol -p
    codex-utils-cli`
  • add encryptedcontent to functioncalloutput (#23500)
    add new `EncryptedContent` variant to `FunctionCallOutputContentItem`
    ahead of standalone websearch.
    
    we need to be able to receive and pass encrypted function call output
    from the new web search endpoint back to responsesapi, as we cannot
    expose direct search results.
  • Clarify resume hints for renamed threads (#23234)
    Addresses #23181
    
    ## Why
    Renamed threads can share names, so hints that suggest resuming directly
    by name are ambiguous. Issue #23181 asks for the picker hint to include
    the thread name and thread ID in parens so users can disambiguate
    safely.
    
    ## What
    - Adds a shared resume hint formatter for named threads: run `codex
    resume`, then select `<name> (<thread-id>)`.
    - Uses that hint for /rename confirmations, TUI session summaries, and
    CLI/TUI exit messages.
    - Keeps direct `codex resume <thread-id>` guidance for unnamed threads.
    
    ## Verification
    Manually verified that message after `/rename` and after `/exit` include
    session ID in parens.
    
    ---------
    
    Co-authored-by: Felipe Coury <felipe.coury@openai.com>
  • tui: pass active permission profiles through app commands (#22891)
    ## Why
    
    This continues the permissions migration by keeping the TUI command
    boundary aligned with the app-server protocol direction from #22795:
    callers should select a permission profile by id instead of passing a
    concrete `PermissionProfile` value around as the turn configuration.
    
    `AppCommand` is internal to the TUI, but it is the path that eventually
    becomes `thread/turn/start`, so carrying concrete profile details there
    made it too easy for UI code to keep relying on the old whole-profile
    replacement model.
    
    ## What changed
    
    - `AppCommand::UserTurn` and `AppCommand::OverrideTurnContext` now carry
    `Option<ActivePermissionProfile>` instead of `PermissionProfile`.
    - Composer submissions copy the active permission profile id from the
    current session snapshot; legacy snapshots intentionally submit no
    active profile id.
    - Permission preset UI events now carry only the active built-in profile
    id. The app derives the concrete built-in `PermissionProfile` internally
    only when updating its local config/status snapshot.
    - Permission presets expose their built-in active profile id, and preset
    selection preserves that id in both the immediate turn override and the
    local TUI config snapshot.
    - Turn routing sends `TurnPermissionsOverride::ActiveProfile` when an
    active id is present, and only falls back to the legacy sandbox
    projection for the remaining runtime override path.
    
    ## How to review
    
    Start with `codex-rs/tui/src/app_command.rs` to verify the command shape
    no longer exposes `PermissionProfile`.
    
    Then read `codex-rs/tui/src/app/thread_routing.rs` to verify the
    app-server turn-start conversion: active ids go through as ids, while
    the legacy sandbox fallback is still constrained to the existing runtime
    override case.
    
    Finally, check `codex-rs/tui/src/chatwidget/permission_popups.rs`,
    `codex-rs/tui/src/app/event_dispatch.rs`,
    `codex-rs/tui/src/app/config_persistence.rs`, and
    `codex-rs/utils/approval-presets/src/lib.rs` to see how preset
    selections stay id-only across TUI events while the local display/config
    mirror still gets a concrete built-in profile.
    
    ## Verification
    
    Latest local verification after the id-only `AppEvent` cleanup:
    
    - `cargo check -p codex-tui --tests`
    - `cargo test -p codex-tui
    permissions_selection_sends_approvals_reviewer_in_override_turn_context`
    - `cargo test -p codex-tui update_feature_flags_enabling_guardian`
    - `cargo test -p codex-utils-approval-presets`
    - `just fmt`
    - `just fix -p codex-tui -p codex-utils-approval-presets`
    
    Earlier in the same PR, before the final event-shape cleanup:
    
    - `cargo test -p codex-tui turn_permissions_`
    - `cargo test -p codex-tui submission_`
    - `cargo test -p codex-tui
    session_configured_syncs_widget_config_permissions_and_cwd`
    - `RUST_MIN_STACK=16777216 cargo test -p codex-tui`
  • tui: recover local state db startup failures (#22734)
    ## Why
    
    #22580 made app-server startup fail when the local SQLite state database
    cannot be initialized. Embedded/local TUI startup still continued on the
    permissive path, which left the CLI inconsistent and could hide a real
    startup problem behind unrelated UI. This brings local TUI startup onto
    the same fail-closed behavior while keeping recovery humane for the two
    failure modes we are seeing in practice: damaged database files and
    startup stalls caused by another process holding the database write
    lock.
    
    ## What changed
    
    - Embedded TUI startup now uses `state_db::try_init(...)` and returns a
    typed `LocalStateDbStartupError` that preserves the affected database
    path plus the underlying failure detail.
    - CLI startup handles that failure before entering the interactive TUI:
    - lock-contention failures tell users to quit other Codex processes and
    try again
    - failures consistent with a broken local database offer a safe repair
    that backs up Codex-owned SQLite files, rebuilds local database files,
    and retries startup once
    - declined or unsuccessful repairs print concise guidance plus technical
    details
    - Shared startup error plumbing lives in `tui/src/startup_error.rs`,
    while CLI recovery policy and focused recovery tests live in
    `cli/src/state_db_recovery.rs`.
    
    ## Verification
    
    - `cargo test -p codex-tui
    embedded_state_db_failure_is_typed_for_cli_recovery`
    - `cargo test -p codex-cli state_db_recovery`
    - Manually held an exclusive SQLite lock on `state_5.sqlite` and
    confirmed the CLI shows lock-specific guidance without offering repair.
    - Manually exercised the repair path with a deliberately invalid
    `sqlite_home` and confirmed it backs up the blocking path and resumes
    startup.