Commit Graph

4014 Commits

  • Implemented thread-level atomic elicitation counter for stopwatch pausing (#12296)
    ### Purpose
    While trying to build out CLI-Tools for the agent to use under skills we
    have found that those tools sometimes need to invoke a user elicitation.
    These elicitations are handled out of band of the codex app-server but
    need to indicate to the exec manager that the command running is not
    going to progress on the usual timeout horizon.
    
    ### Example
    Model calls universal exec:
    `$ download-credit-card-history --start-date 2026-01-19 --end-date
    2026-02-19 > credit_history.jsonl`
    
    download-cred-card-history might hit a hosted/preauthenticated service
    to fetch data. That service might decide that the request requires an
    end user approval the access to the personal data. It should be able to
    signal to the running thread that the command in question is blocked on
    user elicitation. In that case we want the exec to continue, but the
    timeout to not expire on the tool call, essentially freezing time until
    the user approves or rejects the command at which point the tool would
    signal the app-server to decrement the outstanding elicitation count.
    Now timeouts would proceed as normal.
    
    ### What's Added
    
    - New v2 RPC methods:
        - thread/increment_elicitation
        - thread/decrement_elicitation
    - Protocol updates in:
        - codex-rs/app-server-protocol/src/protocol/common.rs
        - codex-rs/app-server-protocol/src/protocol/v2.rs
    - App-server handlers wired in:
        - codex-rs/app-server/src/codex_message_processor.rs
    
    ### Behavior
    
    - Counter starts at 0 per thread.
    - increment atomically increases the counter.
    - decrement atomically decreases the counter; decrement at 0 returns
    invalid request.
    - Transition rules:
    - 0 -> 1: broadcast pause state, pausing all active stopwatches
    immediately.
        - \>0 -> >0: remain paused.
        - 1 -> 0: broadcast unpause state, resuming stopwatches.
    - Core thread/session logic:
        - codex-rs/core/src/codex_thread.rs
        - codex-rs/core/src/codex.rs
        - codex-rs/core/src/mcp_connection_manager.rs
    
    ### Exec-server stopwatch integration
    
    - Added centralized stopwatch tracking/controller:
        - codex-rs/exec-server/src/posix/stopwatch_controller.rs
    - Hooked pause/unpause broadcast handling + stopwatch registration:
        - codex-rs/exec-server/src/posix/mcp.rs
        - codex-rs/exec-server/src/posix/stopwatch.rs
        - codex-rs/exec-server/src/posix.rs
  • [apps] Fix apps enablement condition. (#14011)
    - [x] Fix apps enablement condition to check both the feature flag and
    that the user is not an API key user.
  • Move exec command truncation into ExecCommandToolOutput (#14169)
    Summary
    - relocate truncation logic for exec command output into the new
    `ExecCommandToolOutput` response helper instead of centralized handler
    code
    - update all affected tools and unified exec handling to use the new
    response item structure and eliminate `Function(FunctionToolOutput)`
    responses
    - adjust context, registry, and handler interfaces to align with the new
    response semantics and error fields
    
    Testing
    - Not run (not requested)
  • feat: support disabling bundled system skills (#13792)
    Support disable bundled system skills with a config:
    
    [skills.bundled]
    enabled = false
  • Export tools module into code mode runner (#14167)
    **Summary**
    - allow `code_mode` to pass enabled tools metadata to the runner and
    expose them via `tools.js`
    - import tools inside JavaScript rather than relying only on globals or
    proxies for nested tool calls
    - update specs, docs, and tests to exercise the new bridge and explain
    the tooling changes
    
    **Testing**
    - Not run (not requested)
  • fix(core) default RejectConfig.request_permissions (#14165)
    ## Summary
    Adds a default here so existing config deserializes
    
    ## Testing
    - [x] Added a unit test
  • Enforce single tool output type in codex handlers (#14157)
    We'll need to associate output schema with each tool. Each tool can only
    have on output type.
  • start of hooks engine (#13276)
    (Experimental)
    
    This PR adds a first MVP for hooks, with SessionStart and Stop
    
    The core design is:
    
    - hooks live in a dedicated engine under codex-rs/hooks
    - each hook type has its own event-specific file
    - hook execution is synchronous and blocks normal turn progression while
    running
    - matching hooks run in parallel, then their results are aggregated into
    a normalized HookRunSummary
    
    On the AppServer side, hooks are exposed as operational metadata rather
    than transcript-native items:
    
    - new live notifications: hook/started, hook/completed
    - persisted/replayed hook results live on Turn.hookRuns
    - we intentionally did not add hook-specific ThreadItem variants
    
    Hooks messages are not persisted, they remain ephemeral. The context
    changes they add are (they get appended to the user's prompt)
  • Add code_mode experimental feature (#13418)
    A much narrower and more isolated (no node features) version of js_repl
  • sort plugins first in menu (#14163)
    we want plugin mentions to show up before others, like apps and skills.
    
    updated tests.
  • Refactor tool output into trait implementations (#14152)
    First state to making tool outputs strongly typed (and `renderable`).
  • make dollar-mention always clarify item category (skill, app, plugin) (#14147)
    #### What
    
    ###### Context + Problem
    
    With the introduction of plugins, we now have one more type of
    `$`-mentionable item in the TUI's popup menu on `$`. Apps, skills, and
    plugins can all have the same user-facing name, and we attempt to
    distinguish with a category tag suffix, like `[App]`. This has a few
    problems:
    
    - We decide to show tags by the text that will be inserted into the
    conversation, not the actual user-visible text, so two visibly-identical
    entries can have no clarifying category tag suffix
    - The category tag is a suffix and commonly gets cut off by long
    descriptions
    - The skill category tag is currently only displayed on repo skills as
    `[Repo]`, which is confusing to most users
    - The plugin category tag is currently `[<marketplace-name>]`, which is
    also confusing to most users
    
    ###### Solution
    - **Always** show a **prefix** category tag that is `[Skill]`, `[App]`,
    or `[Plugin]`. No conditional rendering or copy.
    
    Before:
    <img width="801" height="153" alt="image"
    src="https://github.com/user-attachments/assets/448e06e7-2af8-4c14-9804-ed1ca17cf514"
    />
    
    After:
    <img width="800" height="118" alt="image"
    src="https://github.com/user-attachments/assets/57895b41-06fe-4d92-887b-68704c5a15fd"
    />
    
    I also feel this clarifies the results at-a-glance while you scroll:
    
    
    https://github.com/user-attachments/assets/cbdd5840-53d9-4656-812c-6e816755e1fd
    
    ### Tests
    Added + updated tests (including snapshots), tested locally
  • fix: keep permissions profiles forward compatible (#14107)
    ## Summary
    - preserve unknown `:special_path` tokens, including nested entries, so
    older Codex builds warn and ignore instead of failing config load
    - fail closed with a startup warning when a permissions profile has
    missing or empty filesystem entries instead of aborting profile
    compilation
    - normalize Windows verbatim paths like `\?\C:\...` before absolute-path
    validation while keeping explicit errors for truly invalid paths
    
    ## Testing
    - just fmt
    - cargo test -p codex-core permissions_profiles_allow
    - cargo test -p codex-core
    normalize_absolute_path_for_platform_simplifies_windows_verbatim_paths
    - cargo test -p codex-protocol
    unknown_special_paths_are_ignored_by_legacy_bridge
    - cargo clippy -p codex-core -p codex-protocol --all-targets -- -D
    warnings
    - cargo clean
  • fix(protocol): preserve legacy workspace-write semantics (#13957)
    ## Summary
    This is a fast follow to the initial `[permissions]` structure.
    
    - keep the new split-policy carveout behavior for narrower non-write
    entries under broader writable roots
    - preserve legacy `WorkspaceWrite` semantics by using a cwd-aware bridge
    that drops only redundant nested readable roots when projecting from
    `SandboxPolicy`
    - route the legacy macOS seatbelt adapter through that same legacy
    bridge so redundant nested readable roots do not become read-only
    carveouts on macOS
    - derive the legacy bridge for `command_exec` using the sandbox root cwd
    rather than the request cwd so policy derivation matches later sandbox
    enforcement
    - add regression coverage for the legacy macOS nested-readable-root case
    
    ## Examples
    ### Legacy `workspace-write` on macOS
    A legacy `workspace-write` policy can redundantly list a nested readable
    root under an already-writable workspace root.
    
    For example, legacy config can effectively mean:
    - workspace root (`.` / `cwd`) is writable
    - `docs/` is also listed in `readable_roots`
    
    The new shared split-policy helper intentionally treats a narrower
    non-write entry under a broader writable root as a carveout for real
    `[permissions]` configs. Without this fast follow, the unchanged macOS
    seatbelt legacy adapter could project that legacy shape into a
    `FileSystemSandboxPolicy` that treated `docs/` like a read-only carveout
    under the writable workspace root. In practice, legacy callers on macOS
    could unexpectedly lose write access inside `docs/`, even though that
    path was writable before the `[permissions]` migration work.
    
    This change fixes that by routing the legacy seatbelt path through the
    cwd-aware legacy bridge, so:
    - legacy `workspace-write` keeps `docs/` writable when `docs/` was only
    a redundant readable root
    - explicit `[permissions]` entries like `'.' = 'write'` and `'docs' =
    'read'` still make `docs/` read-only, which is the new intended
    split-policy behavior
    
    ### Legacy `command_exec` with a subdirectory cwd
    `command_exec` can run a command from a request cwd that is narrower
    than the sandbox root cwd.
    
    For example:
    - sandbox root cwd is `/repo`
    - request cwd is `/repo/subdir`
    - legacy policy is still `workspace-write` rooted at `/repo`
    
    Before this fast follow, `command_exec` derived the legacy bridge using
    the request cwd, but the sandbox was later built using the sandbox root
    cwd. That mismatch could miss redundant legacy readable roots during
    projection and accidentally reintroduce read-only carveouts for paths
    that should still be writable under the legacy model.
    
    This change fixes that by deriving the legacy bridge with the same
    sandbox root cwd that sandbox enforcement later uses.
    
    ## Verification
    - `just fmt`
    - `cargo test -p codex-core
    seatbelt_legacy_workspace_write_nested_readable_root_stays_writable`
    - `cargo test -p codex-core test_sandbox_config_parsing`
    - `cargo clippy -p codex-core -p codex-app-server --all-targets -- -D
    warnings`
    - `cargo clean`
  • feat(approvals) RejectConfig for request_permissions (#14118)
    ## Summary
    We need to support allowing request_permissions calls when using
    `Reject` policy
    
    <img width="1133" height="588" alt="Screenshot 2026-03-09 at 12 06
    40 PM"
    src="https://github.com/user-attachments/assets/a8df987f-c225-4866-b8ab-5590960daec5"
    />
    
    Note that this is a backwards-incompatible change for Reject policy. I'm
    not sure if we need to add a default based on our current use/setup
    
    ## Testing
    - [x] Added tests
    - [x] Tested locally
  • fix(core) RequestPermissions + ApplyPatch (#14055)
    ## Summary
    The apply_patch tool should also respect AdditionalPermissions
    
    ## Testing
    - [x] Added unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • codex-rs/app-server: add health endpoints for --listen websocket server (#13782)
    Healthcheck endpoints for the websocket server
    
    - serve `GET /readyz` and `GET /healthz` from the same listener used for
    `--listen ws://...`
    - switch the websocket listener over to `axum` upgrade handling instead
    of manual socket parsing
    - add websocket transport coverage for the health endpoints and document
    the new behavior
    
    Testing
    - integration tests
    - built and tested e2e
    
    ```
    > curl -i http://127.0.0.1:9234/readyz
    HTTP/1.1 200 OK
    content-length: 0
    date: Fri, 06 Mar 2026 19:20:23 GMT
    
    >  curl -i http://127.0.0.1:9234/healthz
    HTTP/1.1 200 OK
    content-length: 0
    date: Fri, 06 Mar 2026 19:20:24 GMT
    ```
  • fix(core): use dedicated types for responsesapi web search tool config (#14136)
    This changes the web_search tool spec in codex-core to use dedicated
    Responses-API payload structs instead of shared config types and custom
    serializers.
    
    Previously, `ToolSpec::WebSearch` stored `WebSearchFilters` and
    `WebSearchUserLocation` directly and relied on hand-written serializers
    to shape the outgoing JSON. This worked, but it mixed config/schema
    types with the OpenAI Responses payload contract and created an easy
    place for drift if those shared types changed later.
    
    ### Why
    This keeps the boundary clearer:
    - app-server/config/schema types stay focused on config
    - Responses tool payload types stay focused on the OpenAI wire format
    
    It also makes the serialization behavior obvious from the structs
    themselves, instead of hiding it in custom serializer functions.
  • feat(core) Persist request_permission data across turns (#14009)
    ## Summary
    request_permissions flows should support persisting results for the
    session.
    
    Open Question: Still deciding if we need within-turn approvals - this
    adds complexity but I could see it being useful
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Stabilize protocol schema fixture generation (#13886)
    ## What changed
    - TypeScript schema fixture generation now goes through in-memory tree
    helpers rather than a heavier on-disk generation path.
    - The comparison logic normalizes generated banner and path differences
    that are not semantically relevant to the exported schema.
    - TypeScript and JSON fixture coverage are split into separate tests,
    and the expensive schema-export tests are serialized in `nextest`.
    
    ## Why this fixes the flake
    - The original fixture coverage mixed several heavy codegen paths into
    one monolithic test and then compared generated output that included
    incidental banner/path differences.
    - On Windows CI, that combination created both runtime pressure and
    output variance unrelated to the schema shapes we actually care about.
    - Splitting the coverage isolates failures by format, in-memory
    generation reduces filesystem churn, normalization strips generator
    noise, and serializing the heavy tests removes parallel resource
    contention.
    
    ## Scope
    - Production helper change plus test changes.
  • pass on save info to model + ui tweaks (#14123)
    Passing on more information to the model for context purposes, to
    streamline image-identification.
  • Stabilize RMCP streamable HTTP readiness tests (#13880)
    ## What changed
    - The RMCP streamable HTTP tests now wait for metadata and tool
    readiness before issuing tool calls.
    - OAuth state is isolated per test home.
    - The helper server startup path now uses bounded bind retries so
    transient `AddrInUse` collisions do not fail the test immediately.
    
    ## Why this fixes the flake
    - The old tests could begin issuing tool requests before the helper
    server had finished advertising its metadata and tools, so the first
    request sometimes raced the server startup sequence.
    - On top of that, shared OAuth state and occasional bind collisions on
    CI runners introduced cross-test environmental noise unrelated to the
    functionality under test.
    - Readiness polling makes the client wait for an observable “server is
    ready” signal, while isolated state and bounded bind retries remove
    external contention that was causing intermittent failures.
    
    ## Scope
    - Test-only change.
  • feat(otel): Centralize OTEL metric names and shared tag builders (#14117)
    This cleans up a bunch of metric plumbing that had started to drift.
    
    The main change is making `codex-otel` the canonical home for shared
    metric definitions and metric tag helpers. I moved the `turn/thread`
    metric names that were still duplicated into the OTEL metric registry,
    added a shared `metrics::tags` module for common tag keys and session
    tag construction, and updated `SessionTelemetry` to build its metadata
    tags through that shared path.
    
    On the codex-core side, TTFT/TTFM now use the shared metric-name
    constants instead of local string definitions. I also switched the
    obvious remaining turn/thread metric callsites over to the shared
    constants, and added a small helper so TTFT/TTFM can attach an optional
    sanitized client.name tag from TurnContext.
    
    This should make follow-on telemetry work less ad hoc:
    - one canonical place for metric names
    - one canonical place for common metric tag keys/builders
    - less duplication between `codex-core` and `codex-otel`
  • chore: plugin/uninstall endpoint (#14111)
    add `plugin/uninstall` app-server endpoint to fully rm plugin from
    plugins cache dir and rm entry from user config file.
    
    plugin-enablement is session-scoped, so uninstalls are only picked up in
    new sessions (like installs).
    
    added tests.
  • fix(ci) Faster shell_command::unicode_output test (#14114)
    ## Summary
    Alternative to #14061 - we need to use a child process on windows to
    correctly validate Powershell behavior.
    
    ## Testing
    - [x] These are tests
  • Stabilize resumed rollout messages (#14060)
    ## What changed
    - add a bounded `resume_until_initial_messages` helper in
    `core/tests/suite/resume.rs`
    - retry the resume call until `initial_messages` contains the fully
    persisted final turn shape before asserting
    
    ## Why this fixes flakiness
    The old test resumed once immediately after `TurnComplete` and sometimes
    read rollout state before the final turn had been persisted. That made
    the assertion race persistence timing instead of checking the resumed
    message shape. The new helper polls for up to two seconds in 10ms steps
    and only asserts once the expected message sequence is actually present,
    so the test waits for the real readiness condition instead of depending
    on a lucky timing window.
    
    ## Scope
    - test-only
    - no production logic change
  • Stabilize guardian approval coverage (#14103)
    ## Summary
    - align the guardian permission test with the actual sandbox policy it
    widens and use a slightly larger Windows-only timeout budget
    - expose the additional-permissions normalization helper to the guardian
    test module
    - replace the guardian popup snapshot assertion with targeted string
    assertions
    
    ## Why this fixes the flake
    This group was carrying two separate sources of drift. The guardian core
    test widened derived sandbox policies without updating the source
    sandbox policy, and it used a Windows command/timeout combination that
    was too tight on slower runners. Separately, the TUI test was
    snapshotting the full popup even though unrelated feature text changes
    were the only thing moving. The new assertions keep coverage on the
    guardian entry itself while removing unrelated snapshot churn.
  • Stabilize interrupted task approval cleanup (#14102)
    ## Summary
    - drain the active turn tasks before clearing pending approvals during
    interruption
    - keep the turn in hand long enough for interrupted tasks to observe
    cancellation first
    
    ## Why this fixes the flake
    Interrupted turns could clear pending approvals too early, which let an
    in-flight approval wait surface as a model-visible rejection before the
    turn emitted `TurnAborted`. Reordering the cleanup removes that race
    without changing the steady-state task model.
  • Stabilize shell approval MCP test (#14101)
    ## Summary
    - replace the Python-based file creation command in the MCP shell
    approval test with native platform commands
    - build the expected command string from the exact argv that the test
    sends
    
    ## Why this fixes the flake
    The old test depended on Python startup and shell quoting details that
    varied across runners. The new version still verifies the same approval
    flow, but it uses `touch` on Unix and `New-Item` on Windows so the
    assertion only depends on the MCP shell command that Codex actually
    forwards.
  • fix: properly handle 401 error in clound requirement fetch. (#14049)
    Handle cloud requirements 401s with the same auth recovery flow as
    normal requests, so permanent refresh failures surface the existing
    user-facing auth message instead of a generic workspace-config load
    error.
  • fix(plugin): Also load curated plugins for TUI. (#14050)
    Also run maybe_start_curated_repo_sync_for_config at TUI start time.
  • Stabilize realtime startup context tests (#13876)
    ## What changed
    - The realtime startup-context tests no longer assume the interesting
    websocket payload is always `connection 1 / request 0`.
    - Instead, they now wait for the first outbound websocket request that
    actually carries `session.instructions`, regardless of which websocket
    connection won the accept-order race on the runner.
    - The env-key fallback test stays serialized because it mutates process
    environment.
    
    ## Why this fixes the flake
    - The old test synchronized on the mirrored `session.updated` client
    event and then inspected a fixed websocket slot.
    - On CI, the response websocket and the realtime websocket can race each
    other during startup. When the response websocket wins that race, the
    fixed slot can contain `response.create` instead of the
    startup-context-bearing `session.update` request the test actually cares
    about.
    - That made the test fail nondeterministically by inspecting the wrong
    request, or by timing out waiting on a secondary event even though the
    real outbound request path was correct.
    - Waiting directly on the first request whose payload includes
    `session.instructions` removes both ordering assumptions and makes the
    assertion line up with the actual contract under test.
    - Separately, serializing the environment-mutating fallback case
    prevents unrelated tests from seeing partially updated auth state.
    
    ## Scope
    - Test-only change.
  • Serialize shell snapshot stdin test (#13878)
    ## What changed
    - `snapshot_shell_does_not_inherit_stdin` now runs under its own serial
    key.
    - The change isolates it from other Unix shell-snapshot tests that also
    interact with stdin.
    
    ## Why this fixes the flake
    - The failure was not a shell-snapshot logic bug. It was shared-stdin
    interference between concurrently executing tests.
    - When multiple tests compete for inherited stdin at the same time, one
    test can observe EOF or consumed input that actually belongs to a
    different test.
    - Running this specific test in a dedicated serial bucket guarantees
    exclusive ownership of stdin, which makes the assertion deterministic
    without weakening coverage.
    
    ## Scope
    - Test-only change.
  • Stabilize thread resume replay tests (#13885)
    ## What changed
    - The thread-resume replay tests now use unchecked mock sequencing so
    the replay flow can complete before the test asserts.
    - They also poll outbound `/responses` request counts and fail
    immediately if replay emits an unexpected extra request.
    
    ## Why this fixes the flake
    - The previous version asserted while the replay machinery was still
    mid-flight, so the test was sometimes checking an intermediate state
    instead of the completed behavior.
    - Strict mock sequencing made that problem worse by forcing the test to
    care about exact sub-step timing rather than about the end result.
    - Letting replay settle and then asserting on stabilized request counts
    makes the test validate the real contract: the replay path finishes and
    does not send extra model requests.
    
    ## Scope
    - Test-only change.
  • Order websocket initialize after handshake (#13943)
    ## What changed
    - `app-server` now sends initialize notifications to the specific
    websocket connection before that connection is marked outbound-ready.
    - `message_processor` now exposes the forwarding hook needed to target
    that initialize delivery path.
    
    ## Why this fixes the flake
    - This was a real websocket ordering bug.
    - The old code allowed “connection is ready for outbound broadcasts” to
    become true before the initialize notification had been routed to the
    intended client.
    - On CI this showed up as a race where tests would occasionally miss or
    misorder initialize delivery depending on scheduler timing.
    - Sending initialize to the exact connection first, then exposing it to
    the general outbound path, removes that race instead of hiding it with
    timing slack.
    
    ## Scope
    - Production logic change.
  • Stabilize plan item app-server tests (#14058)
    ## What changed
    - run the two plan-mode app-server tests on a multi-thread Tokio runtime
    instead of the default single-thread test runtime
    - stop relying on wiremock teardown expectations for `/responses` and
    explicitly wait for the expected request count after the turn completes
    
    ## Why this fixes the flake
    - this failure was showing up on Windows ARM as a late wiremock panic
    saying the mock server saw zero `/responses` calls, but the real issue
    was that the test could stall around app-server startup and only fail
    during teardown
    - moving these tests to the same multi-thread runtime used by the other
    collaboration-mode app-server tests removes that startup scheduling race
    - asserting the `/responses` count directly makes the test
    deterministic: we now wait for the real POST instead of depending on a
    drop-time verification that can hide the underlying timing issue
    
    ## Scope
    - test-only change; no production logic changes
  • Stabilize PTY Python REPL test (#13883)
    ## What changed
    - The PTY Python REPL test now starts Python with a startup marker
    already embedded in argv.
    - The test waits for that marker in PTY output before making assertions.
    
    ## Why this fixes the flake
    - The old version tried to probe the live REPL almost immediately after
    spawn.
    - That races PTY initialization, Python startup, and prompt buffering,
    all of which vary across platforms and CI load.
    - By having the child process emit a known marker as part of its own
    startup path, the test gets a deterministic synchronization point that
    comes from the process under test rather than from guessed timing.
    
    ## Scope
    - Test-only change.
  • Stabilize RMCP pid file cleanup test (#13881)
    ## What changed
    - The pid-file cleanup test now keeps polling when the pid file exists
    but is still empty.
    - Assertions only proceed once the wrapper has actually written the
    child pid.
    
    ## Why this fixes the flake
    - File creation and pid writing are not atomic as one logical action
    from the test’s point of view.
    - The previous test sometimes won the race and read the file in the tiny
    window after creation but before the pid bytes were flushed.
    - Treating “empty file” as “not ready yet” synchronizes the test on the
    real event we need: the wrapper has finished publishing the child pid.
    
    ## Scope
    - Test-only change.
  • Stabilize zsh fork app-server tests (#13872)
    ## What changed
    - `turn_start_shell_zsh_fork_executes_command_v2` now keeps the shell
    command alive with a file marker until the interrupt arrives instead of
    using a command that can finish too quickly.
    -
    `turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2`
    now waits for `turn/completed` before sending a fallback interrupt and
    accepts the real terminal outcomes observed across platforms.
    
    ## Why this fixes the flake
    - The original tests assumed a narrow ordering window: the child command
    would still be running when the interrupt happened, and completion would
    always arrive in one specific order.
    - In CI, especially across different shells and runner speeds, those
    assumptions break. Sometimes the child finishes before the interrupt;
    sometimes the protocol completes while the fallback path is still arming
    itself.
    - Holding the command open until the interrupt and waiting for the
    explicit protocol completion event makes the tests synchronize on the
    behavior under test instead of on wall-clock timing.
    
    ## Scope
    - Test-only change.
  • Reduce app-server test timeout pressure (#13884)
    ## What changed
    - The auth/account/fuzzy-file-search test configs disable unrelated
    `shell_snapshot` setup.
    - The fuzzy-file-search fixture set was reduced so the stop-updates test
    does less incidental work before reaching the assertion.
    
    ## Why this fixes the flake
    - These failures were caused by cumulative timeout pressure, not by a
    missing product-level delay.
    - The old tests were paying for shell snapshot initialization and extra
    fixture volume that were not part of the behavior being validated.
    - Removing that incidental work keeps the same coverage but shortens the
    critical path enough that the tests finish comfortably inside the
    existing timeout budget, which is the right fix versus simply extending
    the timeout.
    
    ## Scope
    - Test-only change.
  • guardian initial feedback / tweaks (#13897)
    ## Summary
    - remove the remaining model-visible guardian-specific `on-request`
    prompt additions so enabling the feature does not change the main
    approval-policy instructions
    - neutralize user-facing guardian wording to talk about automatic
    approval review / approval requests rather than a second reviewer or
    only sandbox escalations
    - tighten guardian retry-context handling so agent-authored
    `justification` stays in the structured action JSON and is not also
    injected as raw retry context
    - simplify guardian review plumbing in core by deleting dead
    prompt-append paths and trimming some request/transcript setup code
    
    ## Notable Changes
    - delete the dead `permissions/approval_policy/guardian.md` append path
    and stop threading `guardian_approval_enabled` through model-facing
    developer-instruction builders
    - rename the experimental feature copy to `Automatic approval review`
    and update the `/experimental` snapshot text accordingly
    - make approval-review status strings generic across shell, patch,
    network, and MCP review types
    - forward real sandbox/network retry reasons for shell and unified-exec
    guardian review, but do not pass agent-authored justification as raw
    retry context
    - simplify `guardian.rs` by removing the one-field request wrapper,
    deduping reasoning-effort selection, and cleaning up transcript entry
    collection
    
    ## Testing
    - `just fmt`
    - full validation left to CI
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Stabilize app list update ordering test (#14052)
    ## Summary
    - make
    `list_apps_waits_for_accessible_data_before_emitting_directory_updates`
    accept the two valid notification paths the server can emit
    - keep rejecting the real bug this test is meant to catch: a
    directory-only `app/list/updated` notification before accessible app
    data is available
    
    ## Why this fixes the flake
    The old test used a fixed `150ms` silence window and assumed the first
    notification after that window had to be the fully merged final update.
    In CI, scheduling occasionally lets accessible app data arrive before
    directory data, so the first valid notification can be an
    accessible-only interim update. That made the test fail even though the
    server behavior was correct.
    
    This change makes the test deterministic by reading notifications until
    the final merged payload arrives. Any interim update is only accepted if
    it contains accessible apps only; if the server ever emits inaccessible
    directory data before accessible data is ready, the test still fails
    immediately.
    
    ## Change type
    - test-only; no production app-list logic changes
  • feat(tui) render request_permissions calls (#14004)
    ## Summary
    Adds support for tui rendering of request_permission calls
    
    <img width="724" height="245" alt="Screenshot 2026-03-08 at 9 04 07 PM"
    src="https://github.com/user-attachments/assets/e1997825-a496-4bfb-bbda-43d0006460a5"
    />
    
    
    ## Testing
    - [x] Added snapshot test
  • fix(bazel) add missing app-server-client BUILD.bazel (#14027)
    ## Summary
    Adds missing BUILD.bazel file for the new app-server-client crate
    
    ## Testing
    - [x] 🤞 that this gets bazel ci to pass
  • Add request permissions tool (#13092)
    Adds a built-in `request_permissions` tool and wires it through the
    Codex core, protocol, and app-server layers so a running turn can ask
    the client for additional permissions instead of relying on a static
    session policy.
    
    The new flow emits a `RequestPermissions` event from core, tracks the
    pending request by call ID, forwards it through app-server v2 as an
    `item/permissions/requestApproval` request, and resumes the tool call
    once the client returns an approved subset of the requested permission
    profile.
  • tui: clarify pending steer follow-ups (#13841)
    ## Summary
    - split the pending input preview into labeled pending-steer and queued
    follow-up sections
    - explain that pending steers submit after the next tool call and that
    Esc can interrupt and send them immediately
    - treat Esc as an interrupt-plus-resubmit path when pending steers
    exist, with updated TUI snapshots and tests
    
    Queues and steers:
    <img width="1038" height="263" alt="Screenshot 2026-03-07 at 10 17
    17 PM"
    src="https://github.com/user-attachments/assets/4ef433ef-27a3-4b7c-ad69-2046f6eb89e6"
    />
    
    After pressing Esc:
    <img width="1046" height="320" alt="Screenshot 2026-03-07 at 10 17
    21 PM"
    src="https://github.com/user-attachments/assets/0f4d89e0-b6b9-486a-9f04-b6021f169ba7"
    />
    
    ## Codex author
    `codex resume 019cc6f4-2cca-7803-b717-8264526dbd97`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(core) patch otel test (#14014)
    ## Summary
    This test was missing the turn completion event in the responses stream,
    so it was hanging. This PR fixes the issue
    
    ## Testing
    - [x] This does update the test
  • app-server: include experimental skill metadata in exec approval requests (#13929)
    ## Summary
    
    This change surfaces skill metadata on command approval requests so
    app-server clients can tell when an approval came from a skill script
    and identify the originating `SKILL.md`.
    
    - add `skill_metadata` to exec approval events in the shared protocol
    - thread skill metadata through core shell escalation and delegated
    approval handling for skill-triggered approvals
    - expose the field in app-server v2 as experimental `skillMetadata`
    - regenerate the JSON/TypeScript schemas and cover the new field in
    protocol, transport, core, and TUI tests
    
    ## Why
    
    Skill-triggered approvals already carry skill context inside core, but
    app-server clients could not see which skill caused the prompt. Sending
    the skill metadata with the approval request makes it possible for
    clients to present better approval UX and connect the prompt back to the
    relevant skill definition.
    
    
    ## example event in app-server-v2
    verified that we see this event when experimental api is on:
    ```
    < {
    <   "id": 11,
    <   "method": "item/commandExecution/requestApproval",
    <   "params": {
    <     "additionalPermissions": {
    <       "fileSystem": null,
    <       "macos": {
    <         "accessibility": false,
    <         "automations": {
    <           "bundle_ids": [
    <             "com.apple.Notes"
    <           ]
    <         },
    <         "calendar": false,
    <         "preferences": "read_only"
    <       },
    <       "network": null
    <     },
    <     "approvalId": "25d600ee-5a3c-4746-8d17-e2e61fb4c563",
    <     "availableDecisions": [
    <       "accept",
    <       "acceptForSession",
    <       "cancel"
    <     ],
    <     "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <     "commandActions": [
    <       {
    <         "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <         "type": "unknown"
    <       }
    <     ],
    <     "cwd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes",
    <     "itemId": "call_jZp3xFpNg4D8iKAD49cvEvZy",
    <     "skillMetadata": {
    <       "pathToSkillsMd": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/SKILL.md"
    <     },
    <     "threadId": "019ccc10-b7d3-7ff2-84fe-3a75e7681e69",
    <     "turnId": "019ccc10-b848-76f1-81b3-4a1fa225493f"
    <   }
    < }`
    ```
    
    & verified that this is the event when experimental api is off:
    ```
    < {
    <   "id": 13,
    <   "method": "item/commandExecution/requestApproval",
    <   "params": {
    <     "approvalId": "5fbbf776-261b-4cf8-899b-c125b547f2c0",
    <     "availableDecisions": [
    <       "accept",
    <       "acceptForSession",
    <       "cancel"
    <     ],
    <     "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <     "commandActions": [
    <       {
    <         "command": "/Applications/ChatGPT.app/Contents/Resources/CodexAppServer_CodexAppServerBundledSkills.bundle/Contents/Resources/skills/apple-notes/scripts/notes_info",
    <         "type": "unknown"
    <       }
    <     ],
    <     "cwd": "/Users/celia/code/codex/codex-rs",
    <     "itemId": "call_OV2DHzTgYcbYtWaTTBWlocOt",
    <     "threadId": "019ccc16-2a2b-7be1-8500-e00d45b892d4",
    <     "turnId": "019ccc16-2a8e-7961-98ec-649600e7d06a"
    <   }
    < }
    ```
  • Add in-process app server and wire up exec to use it (#14005)
    This is a subset of PR #13636. See that PR for a full overview of the
    architectural change.
    
    This PR implements the in-process app server and modifies the
    non-interactive "exec" entry point to use the app server.
    
    ---------
    
    Co-authored-by: Felipe Coury <felipe.coury@gmail.com>