Commit Graph

4295 Commits

  • feat(otel, core): record turn TTFT and TTFM metrics in codex-core (#13630)
    ### Summary
    This adds turn-level latency metrics for the first model output and the
    first completed agent message.
    - `codex.turn.ttft.duration_ms` starts at turn start and records on the
    first output signal we see from the model. That includes normal
    assistant text, reasoning deltas, and non-text outputs like tool-call
    items.
    - `codex.turn.ttfm.duration_ms` also starts at turn start, but it
    records when the first agent message finishes streaming rather than when
    its first delta arrives.
    
    ### Implementation notes
    The timing is tracked in codex-core, not app-server, so the definition
    stays consistent across CLI, TUI, and app-server clients.
    
    I reused the existing turn lifecycle boundary that already drives
    `codex.turn.e2e_duration_ms`, stored the turn start timestamp in turn
    state, and record each metric once per turn.
    
    I also wired the new metric names into the OTEL runtime metrics summary
    so they show up in the same in-memory/debug snapshot path as the
    existing timing metrics.
  • fix(app-server): fix turn_start_shell_zsh_fork_executes_command_v2 flake (#13770)
    This fixes a flaky `turn_start_shell_zsh_fork_executes_command_v2` test.
    
    The interrupt path can race with the follow-up `/responses` request that
    reports the aborted tool call, so the test now allows that extra no-op
    response instead of assuming there will only ever be one request. The
    assertions still stay focused on the behavior the test actually cares
    about: starting the zsh-forked command correctly.
    
    Testing:
    - `just fmt`
    - `cargo test -p codex-app-server --test all
    suite::v2::turn_start_zsh_fork::turn_start_shell_zsh_fork_executes_command_v2
    -- --exact --nocapture`
  • Clarify sandbox permission override helper semantics (#13703)
    ## Summary
    Today `SandboxPermissions::requires_additional_permissions()` does not
    actually mean "is `WithAdditionalPermissions`". It returns `true` for
    any non-default sandbox override, including `RequireEscalated`. That
    broad behavior is relied on in multiple `main` callsites.
    
    The naming is security-sensitive because `SandboxPermissions` is used on
    shell-like tool calls to tell the executor how a single command should
    relate to the turn sandbox:
    - `UseDefault`: run with the turn sandbox unchanged
    - `RequireEscalated`: request execution outside the sandbox
    - `WithAdditionalPermissions`: stay sandboxed but widen permissions for
    that command only
    
    ## Problem
    The old helper name reads as if it only applies to the
    `WithAdditionalPermissions` variant. In practice it means "this command
    requested any explicit sandbox override."
    
    That ambiguity made it easy to read production checks incorrectly and
    made the guardian change look like a standalone `main` fix when it is
    not.
    
    On `main` today:
    - `shell` and `unified_exec` intentionally reject any explicit
    `sandbox_permissions` request unless approval policy is `OnRequest`
    - `exec_policy` intentionally treats any explicit sandbox override as
    prompt-worthy in restricted sandboxes
    - tests intentionally serialize both `RequireEscalated` and
    `WithAdditionalPermissions` as explicit sandbox override requests
    
    So changing those callsites from the broad helper to a narrow
    `WithAdditionalPermissions` check would be a behavior change, not a pure
    cleanup.
    
    ## What This PR Does
    - documents `SandboxPermissions` as a per-command sandbox override, not
    a generic permissions bag
    - adds `requests_sandbox_override()` for the broad meaning: anything
    except `UseDefault`
    - adds `uses_additional_permissions()` for the narrow meaning: only
    `WithAdditionalPermissions`
    - keeps `requires_additional_permissions()` as a compatibility alias to
    the broad meaning for now
    - updates the current broad callsites to use the accurately named broad
    helper
    - adds unit coverage that locks in the semantics of all three helpers
    
    ## What This PR Does Not Do
    This PR does not change runtime behavior. That is intentional.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat: limit number of rows per log (#13763)
    avoid DB explosion. This is a temp solution
  • [rmcp-client] Recover from streamable HTTP 404 sessions (#13514)
    ## Summary
    - add one-time session recovery in `RmcpClient` for streamable HTTP MCP
    `404` session expiry
    - rebuild the transport and retry the failed operation once after
    reinitializing the client state
    - extend the test server and integration coverage for `404`, `401`,
    single-retry, and non-session failure scenarios
    
    ## Testing
    - just fmt
    - cargo test -p codex-rmcp-client (the post-rebase run lost its final
    summary in the terminal; the suite had passed earlier before the rebase)
    - just fix -p codex-rmcp-client
  • Add timestamps to feedback log lines (#13688)
    `/feedback` uploads can include `codex-logs.log` from the in-memory
    feedback logger path. That logger was emitting level + message without a
    timestamp, which made some uploaded logs much harder to inspect. This
    change makes the feedback logger use an explicit timer so
    feedback-captured log lines include timestamps consistently.
    
    This is not Windows-specific code. The bug showed up in Windows reports
    because those uploads were hitting the feedback-buffer path more often,
    while Linux/macOS reports were typically coming from the SQLite feedback
    export, which already prefixes timestamps.
    
    Here's an example of a log that is missing the timestamps:
    
    ```
    TRACE app-server request: getAuthStatus
    TRACE app-server request: model/list
     INFO models cache: evaluating cache eligibility
     INFO models cache: attempting load_fresh
     INFO models cache: loaded cache file
     INFO models cache: cache version mismatch
     INFO models cache: no usable cache entry
    DEBUG 
     INFO models cache: cache miss, fetching remote models
    TRACE windows::current_platform is called
    TRACE Returning Info { os_type: Windows, version: Semantic(10, 0, 26200), edition: Some("Windows 11 Professional"), codename: None, bitness: X64, architecture: Some("x86_64") }
    ```
  • [elicitations] Switch to use MCP style elicitation payload for mcp tool approvals. (#13621)
    - [x] Switch to use MCP style elicitation payload for mcp tool
    approvals.
    - [ ] TODO: Update the UI to support the full spec.
  • Enabling CWD Saving for Image-Gen (#13607)
    Codex now saves the generated image on to your current working
    directory.
  • change sound (#13697)
    # External (non-OpenAI) Pull Request Requirements
    
    Before opening this Pull Request, please read the dedicated
    "Contributing" markdown file or your PR may be closed:
    https://github.com/openai/codex/blob/main/docs/contributing.md
    
    If your PR conforms to our contribution guidelines, replace this text
    with a detailed and high quality description of your changes.
    
    Include a link to a bug report or enhancement request.
  • check app auth in plugin/install (#13685)
    #### What
    on `plugin/install`, check if installed apps are already authed on
    chatgpt, and return list of all apps that are not. clients can use this
    list to trigger auth workflows as needed.
    
    checks are best effort based on `codex_apps` loading, much like
    `app/list`.
    
    #### Tests
    Added integration tests, tested locally.
  • fix(tui) remove config check for trusted setting (#11874)
    ## Summary
    Simplify the trusted directory flow. This logic was originally designed
    several months ago, to determine if codex should start in read-only or
    workspace-write mode. However, that's no longer the purpose of directory
    trust - and therefore we should get rid of this logic.
    
    ## Testing
    - [x] Unit tests pass
  • copy current exe to CODEX_HOME/.sandbox-bin for apply_patch (#13669)
    We do this for codex-command-runner.exe as well for the same reason.
    Windows sandbox users cannot execute binaries in the WindowsApp/
    installed directory for the Codex App. This causes apply-patch to fail
    because it tries to execute codex.exe as the sandbox user.
  • refactor: remove proxy admin endpoint (#13687)
    ## Summary
    - delete the network proxy admin server and its runtime listener/task
    plumbing
    - remove the admin endpoint config, runtime, requirement, protocol,
    schema, and debug-surface fields
    - update proxy docs to reflect the remaining HTTP and SOCKS listeners
    only
  • fix: accept two macOS automation input shapes for approval payload compatibility (#13683)
    ## Summary
    This PR:
    1. fixes a deserialization mismatch for macOS automation permissions in
    approval payloads by making core parsing accept both supported wire
    shapes for bundle IDs.
    2. added `#[serde(default)]` to `MacOsSeatbeltProfileExtensions` so
    omitted fields deserialize to secure defaults.
    
    
    ## Why this change is needed
    `MacOsAutomationPermission` uses `#[serde(try_from =
    "MacOsAutomationPermissionDe")]`, so deserialization is controlled by
    `MacOsAutomationPermissionDe`. After we aligned v2
    `additionalPermissions.macos.automations` to the core shape, approval
    payloads started including `{ "bundle_ids": [...] }` in some paths.
    `MacOsAutomationPermissionDe` previously accepted only `"none" | "all"`
    or a plain array, so object-shaped bundle IDs failed with `data did not
    match any variant of untagged enum MacOsAutomationPermissionDe`. This
    change restores compatibility by accepting both forms while preserving
    existing normalization behavior (trim values and map empty bundle lists
    to `None`).
    
    ## Validation
    
    saw this error went away when running
    ```
    cargo run -p codex-app-server-test-client -- \
        --codex-bin ./target/debug/codex \
        -c 'approval_policy="on-request"' \
        -c 'features.shell_zsh_fork=true' \
        -c 'zsh_path="/tmp/codex-zsh-fork/package/vendor/aarch64-apple-darwin/zsh/macos-15/zsh"' \
        send-message-v2 --experimental-api \
        'Use $apple-notes and run scripts/notes_info now.'
    ```
    :
    ```
    Error: failed to deserialize ServerRequest from JSONRPCRequest
    
    Caused by:
        data did not match any variant of untagged enum MacOsAutomationPermissionDe
    ```
  • chore: remove unused legacy macOS permission types (#13677)
    ## Summary
    
    This PR removes legacy macOS permission model types from
    `codex-rs/protocol/src/models.rs`:
    
    - `MacOsPermissions`
    - `MacOsPreferencesValue`
    - `MacOsAutomationValue`
    
    The protocol now relies on the current `MacOsSeatbeltProfileExtensions`
    model for macOS permission data.
  • support plugin/list. (#13540)
    Introduce a plugin/list which reads from local marketplace.json.
    Also update the signature for plugin/install.
  • tui: sort resume picker by last updated time (#13654)
    ## Summary
    - default the resume picker sort key to UpdatedAt instead of CreatedAt
    - keep Tab sort toggling behavior and update the test expectation for
    the new default
    
    ## Testing
    - just fmt
    - cargo test -p codex-tui
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add timestamped SQLite /feedback logs without schema changes (#13645)
    ## Summary
    - keep the SQLite schema unchanged (no migrations)
    - add timestamps to SQLite-backed `/feedback` log exports
    - keep the existing SQL-side byte cap behavior and newline handling
    - document the remaining fidelity gap (span prefixes + structured
    fields) with TODOs
    
    ## Details
    - update `query_feedback_logs` to format each exported line as:
      - `YYYY-MM-DDTHH:MM:SS.ffffffZ {level} {message}`
    - continue scoping rows to requested-thread + same-process threadless
    logs
    - continue capping in SQL before returning rows
    - keep the existing fallback behavior unchanged when SQLite returns no
    rows
    - update parity tests to normalize away the new timestamp prefix while
    we still only store `message`
    
    ## Follow-up
    - TODO already in code: persist enough span/event metadata in SQLite to
    reproduce span prefixes and structured fields in `/feedback` exports
    
    ## Testing
    - `cargo test -p codex-state`
    - `just fmt`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix(tui): clean up pending steer preview wrapping (#13642)
    ## Summary
    - render pending steer previews with a single `pending steer:` prefix
    instead of repeating it for each source line
    - reuse the same truncation path for pending steers and queued drafts so
    multiline previews behave consistently
    - add snapshot coverage for the multiline pending steer case
    
    Before
    <img width="969" height="219" alt="Screenshot 2026-03-05 at 3 55 11 PM"
    src="https://github.com/user-attachments/assets/b062c9c8-43d3-4a52-98e0-3c7643d1697b"
    />
    
    After
    <img width="965" height="203" alt="Screenshot 2026-03-05 at 3 56 08 PM"
    src="https://github.com/user-attachments/assets/40935863-55b3-444f-9e14-1ac63126b2e1"
    />
    
    ## Codex author
    `codex resume 019cc054-385e-79a3-bb85-ec9499623bd8`
    
    Co-authored-by: Codex <noreply@openai.com>
  • Replay thread rollback from rollout history (#13615)
    - Replay thread rollback from the persisted rollout history instead of
    truncating in-memory state.\n- Add rollback coverage, including
    rollback-behind-compaction snapshot coverage.
  • Refine realtime startup context formatting (#13560)
    ## Summary
    - group recent work by git repo when available, otherwise by directory
    - render recent work as bounded user asks with per-thread cwd context
    - exclude hidden files and directories from workspace trees
  • feat(otel): safe tracing (#13626)
    ### Motivation
    Today config.toml has three different OTEL knobs under `[otel]`:
    - `exporter` controls where OTEL logs go
    - `trace_exporter` controls where OTEL traces go
    - `metrics_exporter` controls where metrics go
    
    Those often (pretty much always?) serve different purposes.
    
    For example, for OpenAI internal usage, the **log exporter** is already
    being used for IT/security telemetry, and that use case is intentionally
    content-rich: tool calls, arguments, outputs, MCP payloads, and in some
    cases user content are all useful there. `log_user_prompt` is a good
    example of that distinction. When it’s enabled, we include raw prompt
    text in OTEL logs, which is acceptable for the security use case.
    
    The **trace exporter** is a different story. The goal there is to give
    OpenAI engineers visibility into latency and request behavior when they
    run Codex locally, without sending sensitive prompt or tool data as
    trace event data. In other words, traces should help answer “what was
    slow?” or “where did time go?”, not “what did the user say?” or “what
    did the tool return?”
    
    The complication is that Rust’s `tracing` crate does not make a hard
    distinction between “logs” and “trace events.” It gives us one
    instrumentation API for logs and trace events (via `tracing::event!`),
    and subscribers decide what gets treated as logs, trace events, or both.
    
    Before this change, our OTEL trace layer was effectively attached to the
    general tracing stream, which meant turning on `trace_exporter` could
    pick up content-rich events that were originally written with logging
    (and the `log_exporter`) in mind. That made it too easy for sensitive
    data to end up in exported traces by accident.
    
    ### Concrete example
    In `otel_manager.rs`, this `tracing::event!` call would be exported in
    both logs AND traces (as a trace event).
    ```
        pub fn user_prompt(&self, items: &[UserInput]) {
            let prompt = items
                .iter()
                .flat_map(|item| match item {
                    UserInput::Text { text, .. } => Some(text.as_str()),
                    _ => None,
                })
                .collect::<String>();
    
            let prompt_to_log = if self.metadata.log_user_prompts {
                prompt.as_str()
            } else {
                "[REDACTED]"
            };
    
            tracing::event!(
                tracing::Level::INFO,
                event.name = "codex.user_prompt",
                event.timestamp = %timestamp(),
                // ...
                prompt = %prompt_to_log,
            );
        }
    ```
    
    Instead of `tracing::event!`, we should now be using `log_event!` and
    `trace_event!` instead to more clearly indicate which sink (logs vs.
    traces) that event should be exported to.
    
    ### What changed
    This PR makes the log and trace export distinct instead of treating them
    as two sinks for the same data.
    
    On the provider side, OTEL logs and traces now have separate
    routing/filtering policy. The log exporter keeps receiving the existing
    `codex_otel` events, while trace export is limited to spans and trace
    events.
    
    On the event side, `OtelManager` now emits two flavors of telemetry
    where needed:
    - a log-only event with the current rich payloads
    - a tracing-safe event with summaries only
    
    It also has a convenience `log_and_trace_event!` macro for emitting to
    both logs and traces when it's safe to do so, as well as log- and
    trace-specific fields.
    
    That means prompts, tool args, tool output, account email, MCP metadata,
    and similar content stay in the log lane, while traces get the pieces
    that are actually useful for performance work: durations, counts, sizes,
    status, token counts, tool origin, and normalized error classes.
    
    This preserves current IT/security logging behavior while making it safe
    to turn on trace export for employees.
    
    ### Full list of things removed from trace export
    - raw user prompt text from `codex.user_prompt`
    - raw tool arguments and output from `codex.tool_result`
    - MCP server metadata from `codex.tool_result` (mcp_server,
    mcp_server_origin)
    - account identity fields like `user.email` and `user.account_id` from
    trace-safe OTEL events
    - `host.name` from trace resources
    - generic `codex.tool_decision` events from traces
    - generic `codex.sse_event` events from traces
    - the full ToolCall debug payload from the `handle_tool_call` span
    
    What traces now keep instead is mostly:
    - spans
    - trace-safe OTEL events
    - counts, lengths, durations, status, token counts, and tool origin
    summaries
  • Update models.json (#13617)
    - Update `models.json` to surface the new model entry.
    - Refresh the TUI model picker snapshot to match the updated catalog
    ordering.
    
    ---------
    
    Co-authored-by: aibrahim-oai <219906144+aibrahim-oai@users.noreply.github.com>
  • core/protocol: add structured macOS additional permissions and merge them into sandbox execution (#13499)
    ## Summary
    - Introduce strongly-typed macOS additional permissions across
    protocol/core/app-server boundaries.
    - Merge additional permissions into effective sandbox execution,
    including macOS seatbelt profile extensions.
    - Expand docs, schema/tool definitions, UI rendering, and tests for
    `network`, `file_system`, and `macos` additional permissions.
  • add @plugin mentions (#13510)
    ## Note-- added plugin mentions via @, but that conflicts with file
    mentions
    
    depends and builds upon #13433.
    
    - introduces explicit `@plugin` mentions. this injects the plugin's mcp
    servers, app names, and skill name format into turn context as a dev
    message.
    - we do not yet have UI for these mentions, so we currently parse raw
    text (as opposed to skills and apps which have UI chips, autocomplete,
    etc.) this depends on a `plugins/list` app-server endpoint we can feed
    the UI with, which is upcoming
    - also annotate mcp and app tool descriptions with the plugin(s) they
    come from. this gives the model a first class way of understanding what
    tools come from which plugins, which will help implicit invocation.
    
    ### Tests
    Added and updated tests, unit and integration. Also confirmed locally a
    raw `@plugin` injects the dev message, and the model knows about its
    apps, mcps, and skills.
  • Clarify js_repl image emission and encoding guidance (#13639)
    ## Summary
    
    This updates the `js_repl` prompt and docs to make the image guidance
    less confusing.
    
    ## What changed
    
    - Clarified that `codex.emitImage(...)` adds one image per call and can
    be called multiple times to emit multiple images.
    - Reworded the image-encoding guidance to be general `js_repl` advice
    instead of `ImageDetailOriginal`-specific behavior.
    - Updated the guidance to recommend JPEG at about quality 85 when lossy
    compression is acceptable, and PNG when transparency or lossless detail
    matters.
    - Mirrored the same wording in the public `js_repl` docs.
  • Improve macOS Seatbelt network and unix socket handling (#12702)
    This improves macOS Seatbelt handling for sandboxed tool processes.
    
    ## Changes
    - Allow dual-stack local binding in proxy-managed sessions, while still
    keeping traffic limited to loopback and configured proxy endpoints.
    - Replace the old generic unix-socket path rule with explicit AF_UNIX
    permissions for socket creation, bind, and outbound connect.
    - Keep explicitly approved wrapper sockets connect-only.
    
    Local helper servers are less likely to fail when binding on macOS.
    Tools using local unix-socket IPC should work more reliably under the
    sandbox.
    Full-network sessions, proxy fail-closed behavior, and proxy lifecycle
    are unchanged.
  • fix(linux-sandbox): always unshare bwrap userns (#13624)
    ## Summary
    - always pass `--unshare-user` in the Linux bubblewrap argv builders
    - stop relying on bubblewrap's auto-userns behavior, which is skipped
    for `uid 0`
    - update argv expectations in tests and document the explicit user
    namespace behavior
    
    The installed Codex binary reproduced the same issue with:
    - `codex -c features.use_linux_sandbox_bwrap=true sandbox linux -- true`
    - `bwrap: Creating new namespace failed: Operation not permitted`
    
    This happens because Codex asked bubblewrap for mount/pid/network
    namespaces without explicitly asking for a user namespace. In a
    root-inside-container environment without ambient `CAP_SYS_ADMIN`, that
    fails. Adding `--unshare-user` makes bubblewrap create the user
    namespace first and then the remaining namespaces succeed.
  • feat(core): persist trace_id for turns in RolloutItem::TurnContext (#13602)
    This PR adds a durable trace linkage for each turn by storing the active
    trace ID on the rollout TurnContext record stored in session rollout
    files.
    
    Before this change, we propagated trace context at runtime but didn’t
    persist a stable per-turn trace key in rollout history. That made
    after-the-fact debugging harder (for example, mapping a historical turn
    to the corresponding trace in datadog). This sets us up for much easier
    debugging in the future.
    
    ### What changed
    - Added an optional `trace_id` to TurnContextItem (rollout schema).
    - Added a small OTEL helper to read the current span trace ID.
    - Captured `trace_id` when creating `TurnContext` and included it in
    `to_turn_context_item()`.
    - Updated tests and fixtures that construct TurnContextItem so
    older/no-trace cases still work.
    
    ### Why this approach
    TurnContext is already the canonical durable per-turn metadata in
    rollout. This keeps ownership clean: trace linkage lives with other
    persisted turn metadata.
  • Harden js_repl emitImage to accept only data: URLs (#13507)
    ### Motivation
    
    - Prevent untrusted js_repl code from supplying arbitrary external URLs
    that the host would forward into model input and cause external fetches
    / data exfiltration. This change narrows the emitImage contract to safe,
    self-contained data URLs.
    
    ### Description
    
    - Kernel: added `normalizeEmitImageUrl` and enforce that string-valued
    `codex.emitImage(...)` inputs and `input_image`/content-item paths only
    accept non-empty `data:` URLs; byte-based paths still produce data URLs
    as before (`kernel.js`).
    - Host: added `validate_emitted_image_url` and check `EmitImage`
    requests before creating `FunctionCallOutputContentItem::InputImage`,
    returning an error to the kernel if the URL is not a `data:` URL
    (`mod.rs`).
    - Tests/docs: added a runtime test
    `js_repl_emit_image_rejects_non_data_url` to assert rejection of
    non-data URLs and updated user-facing docs/instruction text to state
    `data URL` support instead of generic direct image URLs (`mod.rs`,
    `docs/js_repl.md`, `project_doc.rs`).
    
    ### Testing
    
    - Ran `just fmt` in `codex-rs`; it completed successfully.
    - Added a runtime test (`cargo test -p codex-core
    js_repl_emit_image_rejects_non_data_url`) but executing the test in this
    environment failed due to a missing system dependency required by
    `codex-linux-sandbox` (the vendored `bubblewrap` build requires
    `libcap.pc` via `pkg-config`), so the test could not be run here.
    - Attempted a focused `cargo test` invocation with and without default
    features; both compile/test attempts were blocked by the same missing
    system `libcap` dependency in this environment.
    
    ------
    [Codex
    Task](https://chatgpt.com/codex/tasks/task_i_69a7837bce98832d91db92d5f76d6cbe)
  • feat: merge skill permission profiles into the turn sandbox for zsh-fork execs (#13496)
    ## Summary
    
    This changes the Unix shell escalation path for skill-matched
    executables to apply a skill's `PermissionProfile` as additive
    permissions on top of the existing turn/request sandbox policy.
    
    Previously, skill-matched executables compiled the skill permission
    profile into a standalone sandbox policy and executed against that
    replacement policy. Now they go through the same
    `additional_permissions` merge path used elsewhere in shell sandbox
    preparation.
    
    ## What Changed
    
    - Changed `skill_escalation_execution()` to return
    `EscalationPermissions::PermissionProfile(...)` for non-empty skill
    permission profiles.
    - Kept empty or missing skill permission profiles on the `TurnDefault`
    path.
    - Added tests covering the new additive skill-permission behavior.
    - Added inline comments in `prepare_escalated_exec()` clarifying the
    difference between additive permission merging and fully specified
    replacement sandbox policies.
    - Removed the now-unused skill permission compiler module after
    switching this path away from standalone compiled skill sandbox
    policies.
    
    ## Testing
    
    - Ran `just fmt` in `codex-rs`
    - Ran `cargo test -p codex-core`
    
    `cargo test -p codex-core` still hits an unrelated existing failure:
    `shell_snapshot::tests::snapshot_shell_does_not_inherit_stdin`
    
    ## Follow-up
    
    This change intentionally does not merge skill-specific macOS seatbelt
    profile extensions through the `additional_permissions` path yet.
    Filesystem and network permissions now follow the additive merge path,
    but seatbelt extension permissions still need separate handling in a
    follow-up PR.
  • [diagnostics] show diagnostics earlier in workflow (#13604)
    <img width="591" height="243" alt="Screenshot 2026-03-05 at 10 17 06 AM"
    src="https://github.com/user-attachments/assets/84a6658b-6017-4602-b1f8-2098b9b5eff9"
    />
    
    - show feedback earlier
    - preserve raw literal env vars (no trimming, sanitizing, etc.)
  • Persist initialized js_repl bindings after failed cells (#13482)
    ## Summary
    
    - Change `js_repl` failed-cell persistence so later cells keep prior
    bindings plus only the current-cell bindings whose initialization
    definitely completed before the throw.
    - Preserve initialized lexical bindings across failed cells via
    module-namespace readability, including top-level destructuring that
    partially succeeds before a later throw.
    - Preserve hoisted `var` and `function` bindings only when execution
    clearly reached their declaration site, and preserve direct top-level
    pre-declaration `var` writes and updates through explicit write-site
    markers.
    - Preserve top-level `for...in` / `for...of` `var` bindings when the
    loop body executes at least once, using a first-iteration guard to avoid
    per-iteration bookkeeping overhead.
    - Keep prior module state intact across link-time failures and
    evaluation failures before the prelude runs, while still allowing failed
    cells that already recreated prior bindings to persist updates to those
    existing bindings.
    - Hide internal commit hooks from user `js_repl` code after the prelude
    aliases them, so snippets cannot spoof committed bindings by calling the
    raw `import.meta` hooks directly.
    - Add focused regression coverage for the supported failed-cell
    behaviors and the intentionally unsupported boundaries.
    - Update `js_repl` docs and generated instructions to describe the new,
    narrower failed-cell persistence model.
    
    ## Motivation
    
    We saw `js_repl` drop bindings that had already been initialized
    successfully when a later statement in the same cell threw, for example:
    
        const { context: liveContext, session } =
          await initializeGoogleSheetsLiveForTab(tab);
        // later statement throws
    
    That was surprising in practice because successful earlier work
    disappeared from the next cell.
    
    This change makes failed-cell persistence more useful without trying to
    model every possible partially executed JavaScript edge case. The
    resulting behavior is narrower and easier to reason about:
    
    - prior bindings are always preserved
    - lexical bindings persist when their initialization completed before
    the throw
    - hoisted `var` / `function` bindings persist only when execution
    clearly reached their declaration or a supported top-level `var` write
    site
    - failed cells that already recreated prior bindings can persist writes
    to those existing bindings even if they introduce no new bindings
    
    The detailed edge-case matrix stays in `docs/js_repl.md`. The
    model-facing `project_doc` guidance is intentionally shorter and focused
    on generation-relevant behavior.
    
    ## Supported Failed-Cell Behavior
    
    - Prior bindings remain available after a failed cell.
    - Initialized lexical bindings remain available after a failed cell.
    - Top-level destructuring like `const { a, b } = ...` preserves names
    whose initialization completed before a later throw.
    - Hoisted `function` bindings persist when execution reached the
    declaration statement before the throw.
    - Direct top-level pre-declaration `var` writes and updates persist, for
    example:
      - `x = 1`
      - `x += 1`
      - `x++`
    - short-circuiting logical assignments only persist when the write
    branch actually runs
    - Non-empty top-level `for...in` / `for...of` `var` loops persist their
    loop bindings.
    - Failed cells can persist updates to existing carried bindings after
    the prelude has run, even when the cell commits no new bindings.
    - Link failures and eval failures before the prelude do not poison
    `@prev`.
    
    ## Intentionally Unsupported Failed-Cell Cases
    
    - Hoisted function reads before the declaration, such as `foo(); ...;
    function foo() {}`
    - Aliasing or inference-based recovery from reads before declaration
    - Nested writes inside already-instrumented assignment RHS expressions
    - Destructuring-assignment recovery for hoisted `var`
    - Partial `var` destructuring recovery
    - Pre-declaration `undefined` reads for hoisted `var`
    - Empty top-level `for...in` / `for...of` loop vars
    - Nested or scope-sensitive pre-declaration `var` writes outside direct
    top-level expression statements
  • treat SIGTERM like ctrl-c for graceful shutdown (#13594)
    treat SIGTERM the same as SIGINT for graceful app-server websocket
    shutdown
  • feat(app-server): support mcp elicitations in v2 api (#13425)
    This adds a first-class server request for MCP server elicitations:
    `mcpServer/elicitation/request`.
    
    Until now, MCP elicitation requests only showed up as a raw
    `codex/event/elicitation_request` event from core. That made it hard for
    v2 clients to handle elicitations using the same request/response flow
    as other server-driven interactions (like shell and `apply_patch`
    tools).
    
    This also updates the underlying MCP elicitation request handling in
    core to pass through the full MCP request (including URL and form data)
    so we can expose it properly in app-server.
    
    ### Why not `item/mcpToolCall/elicitationRequest`?
    This is because MCP elicitations are related to MCP servers first, and
    only optionally to a specific MCP tool call.
    
    In the MCP protocol, elicitation is a server-to-client capability: the
    server sends `elicitation/create`, and the client replies with an
    elicitation result. RMCP models it that way as well.
    
    In practice an elicitation is often triggered by an MCP tool call, but
    not always.
    
    ### What changed
    - add `mcpServer/elicitation/request` to the v2 app-server API
    - translate core `codex/event/elicitation_request` events into the new
    v2 server request
    - map client responses back into `Op::ResolveElicitation` so the MCP
    server can continue
    - update app-server docs and generated protocol schema
    - add an end-to-end app-server test that covers the full round trip
    through a real RMCP elicitation flow
    - The new test exercises a realistic case where an MCP tool call
    triggers an elicitation, the app-server emits
    mcpServer/elicitation/request, the client accepts it, and the tool call
    resumes and completes successfully.
    
    ### app-server API flow
    - Client starts a thread with `thread/start`.
    - Client starts a turn with `turn/start`.
    - App-server sends `item/started` for the `mcpToolCall`.
    - While that tool call is in progress, app-server sends
    `mcpServer/elicitation/request`.
    - Client responds to that request with `{ action: "accept" | "decline" |
    "cancel" }`.
    - App-server sends `serverRequest/resolved`.
    - App-server sends `item/completed` for the mcpToolCall.
    - App-server sends `turn/completed`.
    - If the turn is interrupted while the elicitation is pending,
    app-server still sends `serverRequest/resolved` before the turn
    finishes.
  • feat: skills for artifacts (#13525)
    Co-authored-by: Dibyo Majumdar <dibyo@openai.com>
  • refactor: prepare unified exec for zsh-fork backend (#13392)
    ## Why
    
    `shell_zsh_fork` already provides stronger guarantees around which
    executables receive elevated permissions. To reuse that machinery from
    unified exec without pushing Unix-specific escalation details through
    generic runtime code, the escalation bootstrap and session lifetime
    handling need a cleaner boundary.
    
    That boundary also needs to be safe for long-lived sessions: when an
    intercepted shell session is closed or pruned, any in-flight approval
    workers and any already-approved escalated child they spawned must be
    torn down with the session, and the inherited escalation socket must not
    leak into unrelated subprocesses.
    
    ## What Changed
    
    - Extracted a reusable `EscalationSession` and
    `EscalateServer::start_session(...)` in `shell-escalation` so callers
    can get the wrapper/socket env overlay and keep the escalation server
    alive without immediately running a one-shot command.
    - Documented that `EscalationSession::env()` and
    `ShellCommandExecutor::run(...)` exchange only that env overlay, which
    callers must merge into their own base shell environment.
    - Clarified the prepared-exec helper boundary in `core` by naming the
    new helper APIs around `ExecRequest`, while keeping the legacy
    `execute_env(...)` entrypoints as thin compatibility wrappers for
    existing callers that still use the older naming.
    - Added a small post-spawn hook on the prepared execution path so the
    parent copy of the inheritable escalation socket is closed immediately
    after both the existing one-shot shell-command spawn and the
    unified-exec spawn.
    - Made session teardown explicit with session-scoped cancellation:
    dropping an `EscalationSession` or canceling its parent request now
    stops intercept workers, and the server-spawned escalated child uses
    `kill_on_drop(true)` so teardown cannot orphan an already-approved
    child.
    - Added `UnifiedExecBackendConfig` plumbing through `ToolsConfig`, a
    `shell::zsh_fork_backend` facade, and an opaque unified-exec
    spawn-lifecycle hook so unified exec can prepare a wrapped `zsh -c/-lc`
    request without storing `EscalationSession` directly in generic
    process/runtime code.
    - Kept the existing `shell_command` zsh-fork behavior intact on top of
    the new bootstrap path. Tool selection is unchanged in this PR: when
    `shell_zsh_fork` is enabled, `ShellCommand` still wins over
    `exec_command`.
    
    ## Verification
    
    - `cargo test -p codex-shell-escalation`
      - includes coverage for `start_session_exposes_wrapper_env_overlay`
      - includes coverage for `exec_closes_parent_socket_after_shell_spawn`
    - includes coverage for
    `dropping_session_aborts_intercept_workers_and_kills_spawned_child`
    - `cargo test -p codex-core
    shell_zsh_fork_prefers_shell_command_over_unified_exec`
    - `cargo test -p codex-core --test all
    shell_zsh_fork_prompts_for_skill_script_execution`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13392).
    * #13432
    * __->__ #13392
  • [tui] Show speed in session header (#13446)
    - add a speed row to the startup/session header under the model row
    - render the speed row with the same styling pattern as the model row,
    using /fast to change
    - show only Fast or Standard to users and update the affected snapshots
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • chore: add web_search_tool_type for image support (#13538)
    add `web_search_tool_type` on model_info that can be populated from
    backend. will be used to filter which models can use `web_search` with
    images and which cant.
    
    added small unit test.
  • Reduce realtime audio submission log noise (#13539)
    - lower `submission_dispatch` span logging to debug for realtime audio
    submissions only
    - keep other submission spans at info and add a targeted test for the
    level selection
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>