Commit Graph

4710 Commits

  • add(core): arc_monitor (#13936)
    ## Summary
    - add ARC monitor support for MCP tool calls by serializing MCP approval
    requests into the ARC action shape and sending the relevant
    conversation/policy context to the `/api/codex/safety/arc` endpoint
    - route ARC outcomes back into MCP approval flow so `ask-user` falls
    back to a user prompt and `steer-model` blocks the tool call, with
    guardian/ARC tests covering the new request shape
    - update the TUI approval copy from “Approve Once” to “Allow” / “Allow
    for this session” and refresh the related
      snapshots
    
    ---------
    
    Co-authored-by: Fouad Matin <fouad@openai.com>
    Co-authored-by: Fouad Matin <169186268+fouad-openai@users.noreply.github.com>
  • Add OpenAI Docs skill (#13596)
    ## Summary
    - add the OpenAI Docs skill under
    codex-rs/skills/src/assets/samples/openai-docs
    - include the skill metadata, assets, and GPT-5.4 upgrade reference
    files
    - exclude the test harness and test fixtures
    
    ## Testing
    - not run (skill-only asset copy)
  • [skill-creator] Add forward-testing instructions (#13600)
    This updates the `skill-creator` sample skill to explicitly cover
    forward-testing as part of the skill authoring workflow. The guidance
    now treats subagent-based validation as a first-class step for complex
    or fragile skills, with an emphasis on preserving evaluation integrity
    and avoiding leaked context.
    
    The sample initialization script is also updated so newly created skills
    point authors toward forward-testing after validation. Together, these
    changes make the sample more opinionated about how skills should be
    iterated on once the initial implementation is complete.
    
    - Add new guidance to `SKILL.md` on protecting validation integrity,
    when to use subagents for forward-testing, and how to structure
    realistic test prompts without leaking expected answers.
    - Expand the skill creation workflow so iteration explicitly includes
    forward-testing for complex skills, including approval guidance for
    expensive or risky validation runs.
  • Mark incomplete resumed turns interrupted when idle (#14125)
    Fixes a Codex app bug where quitting the app mid-run could leave the
    reopened thread stuck in progress and non-interactable. On cold thread
    resume, app-server could return an idle thread with a replayed turn
    still marked in progress. This marks incomplete replayed turns as
    interrupted unless the thread is actually active.
  • Reuse McpToolOutput in McpHandler (#14229)
    We already have a type to represent the MCP tool output, reuse it
    instead of the custom McpHandlerOutput
  • Stabilize split PTY output on Windows (#14003)
    ## Summary
    - run the split stdout/stderr PTY test through the normal shell helper
    on every platform
    - use a Windows-native command string instead of depending on Python to
    emit split streams
    - assert CRLF line endings on Windows explicitly
    
    ## Why this fixes the flake
    The earlier PTY split-output test used a Python one-liner on Windows
    while the rest of the file exercised shell-command behavior. That made
    the test depend on runner-local Python availability and masked the real
    Windows shell output shape. Using a native cmd-compatible command and
    asserting the actual CRLF output makes the split stdout/stderr coverage
    deterministic on Windows runners.
  • Expose strongly-typed result for exec_command (#14183)
    Summary
    - document output types for the various tool handlers and registry so
    the API exposes richer descriptions
    - update unified execution helpers and client tests to align with the
    new output metadata
    - clean up unused helpers across tool dispatch paths
    
    Testing
    - Not run (not requested)
  • Log ChatGPT user ID for feedback tags (#13901)
    There are some bug investigations that currently require us to ask users
    for their user ID even though they've already uploaded logs and session
    details via `/feedback`. This frustrates users and increases the time
    for diagnosis.
    
    This PR includes the ChatGPT user ID in the metadata uploaded for
    `/feedback` (both the TUI and app-server).
  • Fix Linux tmux segfault in user shell lookup (#13900)
    Replace the Unix shell lookup path in `codex-rs/core/src/shell.rs` to
    use
    `libc::getpwuid_r()` instead of `libc::getpwuid()` when resolving the
    current
    user's shell.
    
    Why:
    - `getpwuid()` can return pointers into libc-managed shared storage
    - on the musl static Linux build, concurrent callers can race on that
    storage
    - this matches the crash pattern reported in tmux/Linux sessions with
    parallel
      shell activity
    
    Refs:
    - Fixes #13842
  • Fix release-mode integration test compiler failure (#13603)
    Addresses #13586
    
    This doesn't affect our CI scripts. It was user-reported.
    
    Summary
    - add `wiremock::ResponseTemplate` and `body_string_contains` imports
    behind `#[cfg(not(debug_assertions))]` in
    `codex-rs/core/tests/suite/view_image.rs` so release builds only pull
    the helpers they actually use
  • Stabilize app-server notify initialize test (#13939)
    ## What changed
    - This PR changes only the flaky test setup for
    `turn_start_notify_payload_includes_initialize_client_name`.
    - Instead of shelling out to `python3` to write the notify payload, the
    test uses the first-party `codex-app-server-test-notify-capture` helper.
    - The helper writes `notify.json` atomically and the test waits for the
    file to exist before reading it.
    
    ## Why this fixes the flake
    - The old test depended on an external Python interpreter being present
    and behaving consistently on every CI runner.
    - It also raced the file write: the test could observe the path before
    the payload had been fully written, which produced partial reads and
    intermittent assertion failures.
    - Moving the write into a repo-owned helper removes the external
    dependency, and atomic write-plus-wait makes the handoff deterministic.
    
    ## Scope
    - Test-only change.
  • Stabilize websocket test server binding (#14002)
    ## Summary
    - stop reserving a localhost port in the websocket tests before spawning
    the server
    - let the app-server bind `127.0.0.1:0` itself and read back the actual
    bound websocket address from stderr
    - update the websocket test helpers and callers to use the discovered
    address
    
    ## Why this fixes the flake
    The previous harness reserved a port in the test process, dropped it,
    and then asked the server process to bind that same address. On busy
    runners there is a race between releasing the reservation and the child
    process rebinding it, which can produce sporadic startup failures.
    Binding to port `0` inside the server removes that race entirely, and
    waiting for the server to report the real bound address makes the tests
    connect only after the listener is actually ready.
  • Fix unified exec test output assertion (#14184)
    ## Summary
    - update the unified exec test to use truncated_output() instead of the
    removed output field
    - fix the compile failure on latest main after ExecCommandToolOutput
    changed shape
  • Stabilize incomplete SSE retry test (#13879)
    ## What changed
    - The retry test now uses the same streaming SSE test server used by
    production-style tests instead of a wiremock sequence.
    - The fixture is resolved via `find_resource!`, and the test asserts
    that exactly two outbound requests were sent.
    
    ## Why this fixes the flake
    - The old wiremock sequence approximated early-close behavior, but it
    did not reproduce the same streaming semantics the real client sees.
    - That meant the retry path depended on mock implementation details
    instead of on the actual transport behavior we care about.
    - Switching to the streaming SSE helper makes the test exercise the real
    early-close/retry contract, and counting requests directly verifies that
    we retried exactly once rather than merely hoping the sequence aligned.
    
    ## Scope
    - Test-only change.
  • Use realtime transcript for handoff context (#14132)
    - collect input/output transcript deltas into active handoff transcript
    state
    - attach and clear that transcript on each handoff, and regenerate
    schema/tests
  • 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
  • Delay pending cleanup until task aborts (#14000)
    ## Summary
    - move interrupted-turn cleanup so running tasks are aborted before
    pending approvals are cleared
    - keep unified exec shutdown behavior unchanged
    
    ## Why this fixes the flake
    The interrupted-turn path could clear pending approvals before the
    in-flight task had observed cancellation. On slower runners that let an
    approval wait resolve in between those steps, tests would sometimes
    surface a model-visible rejection instead of the expected TurnAborted
    flow. Draining the active turn first and only then clearing pending
    approval state makes the abort ordering deterministic.
  • [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.