Commit Graph

6938 Commits

  • [codex] Avoid PowerShell safety parsing off Windows (#24946)
    ## Summary
    
    This fixes BUGB-17567 by preventing non-Windows command safety
    classification from invoking the Windows PowerShell safelist/parser
    path.
    
    Previously, `is_known_safe_command` called the Windows PowerShell
    classifier on every platform. That classifier recognizes
    `pwsh`/`powershell` by basename and delegates script parsing to the
    PowerShell AST parser. The parser starts the supplied executable, so on
    macOS/Linux a repository-controlled `pwsh` path could execute during
    safety parsing before the normal sandboxed command execution path.
    
    The change gates the Windows PowerShell classifier and module behind
    `#[cfg(windows)]`. On macOS/Linux, PowerShell-looking commands are no
    longer auto-approved by the Windows classifier and instead fall through
    to the normal non-Windows safe-command logic.
    
    ## Validation
    
    - `/private/tmp/codex-tools/bin/just fmt`
    - `PATH=/private/tmp/codex-tools/bin:$PATH
    /private/tmp/codex-tools/bin/just test -p codex-shell-command`
    
    The focused test run passed 135 tests with 0 skipped and completed the
    crate bench-smoke step.
    
    ## Notes
    
    This PR is scoped to the BUGB-17567 macOS/Linux path. Windows still uses
    the PowerShell classifier; a separate hardening follow-up should ensure
    Windows safety parsing only executes a trusted PowerShell parser binary
    and does not spawn the command's `argv[0]` when that path may be
    repository-controlled.
  • fix(config): use deny for Unix socket permissions (#24970)
    ## Why
    
    Unix socket permissions still accepted and displayed `"none"` while file
    permissions use the clearer `"deny"` spelling. This keeps network Unix
    socket policy vocabulary consistent with filesystem policy vocabulary.
    
    ## What changed
    
    - Replace the Unix socket permission variant and serialized spelling
    from `none` to `deny` across config, feature configuration, and network
    proxy types.
    - Update app-server v2 serialization, TUI debug output, focused tests,
    and generated schemas to expose `"deny"`.
    - Add coverage for denied Unix socket entries in managed requirements
    and profile overlay behavior.
    
    ## Security
    
    This is a vocabulary change for explicit Unix socket rejection, not a
    network access expansion. Denied entries continue to be omitted from the
    effective allowlist.
    
    ## Validation
    
    - `just fmt`
    - `just write-config-schema`
    - `just write-app-server-schema`
    - `just test -p codex-config -p codex-core -p codex-app-server-protocol
    -p codex-tui -E
    'test(network_requirements_are_preserved_as_constraints_with_source) |
    test(network_permission_containers_project_allowed_and_denied_entries) |
    test(network_toml_overlays_unix_socket_permissions_by_path) |
    test(permissions_profiles_resolve_extends_parent_first_with_child_overrides)
    | test(network_requirements_serializes_canonical_and_legacy_fields) |
    test(debug_config_output_formats_unix_socket_permissions)'`\n- Automatic
    `bench-smoke` follow-up from `just test`\n- `cargo clippy -p
    codex-config -p codex-core -p codex-features -p codex-network-proxy -p
    codex-app-server-protocol -p codex-app-server -p codex-tui --all-targets
    -- -D warnings`
  • feat(app-server): migrate remote control to server tokens (#24141)
    ## Why
    
    `codex-backend` now authenticates remote-control server websocket
    connections with short-lived server tokens instead of the user's ChatGPT
    access token. `app-server` needs to mint and refresh those server tokens
    without persisting them, so a restart can reconnect from durable
    enrollment identity while keeping the bearer token memory-only.
    
    ## What Changed
    
    Updated the remote-control transport to consume `remote_control_token`
    and `expires_at` from server enroll responses and added
    `/server/refresh` support for persisted enrollments or expiring cached
    tokens.
    
    Websocket handshakes now send `Authorization: Bearer
    <remote_control_token>` with the existing server identity headers, and
    no longer send the ChatGPT bearer token or `chatgpt-account-id` on that
    websocket path.
    
    The in-memory enrollment state now owns the ephemeral server token
    cache, while SQLite still persists only `server_id`, `environment_id`,
    and `server_name`. Websocket `401`/`403` clears only the cached token
    for refresh on reconnect; websocket or refresh `404` clears stale
    persisted enrollment and re-enrolls. Response body previews redact
    `remote_control_token` before surfacing parse errors.
    
    ## Verification
    
    - `just test -p codex-app-server-transport`
    - Manual prod smoke with an isolated `CODEX_HOME`: `codex remote-control
    --json -c 'chatgpt_base_url="https://chatgpt.com/backend-api"'` reached
    `status:"connected"` with
    `environmentId:"env_i_6a17d9f1d764832986da2e80f4554f1b"`.
  • Tighten hook output event schemas (#24962)
    # Why
    
    Fixes #23993.
    
    Hook command output schemas are published as the contract for hook
    authors and schema-driven tooling. The event-specific output schemas
    previously described `hookSpecificOutput.hookEventName` as the global
    `HookEventNameWire` enum, so a `pre-tool-use.command.output` schema
    would validate mismatched values like `PostToolUse`. That made the
    schemas less precise than the intended event-specific contract.
    
    # What
    
    Constrain each hook-specific output schema to the matching literal
    `hookEventName` value, mirroring the existing input-schema shape.
    
    Also split `SubagentStartHookSpecificOutputWire` from the session-start
    output wire so `subagent-start.command.output.schema.json` can emit
    `const: "SubagentStart"` instead of sharing the session-start
    definition.
    
    # Verification
    
    - `cargo nextest run -p codex-hooks`
    - `just fix -p codex-hooks`
    - `just argument-comment-lint -p codex-hooks -- --all-targets`
  • windows-sandbox: fix capture cancellation test roots (#24974)
    ## Why
    
    The Windows Bazel job on `main` started failing after #24108 because one
    Windows-only capture test still passed `cwd.as_path()` to
    `run_windows_sandbox_capture`. That helper now expects the explicit
    `workspace_roots` slice introduced by #24108, so the Windows test target
    no longer compiled.
    
    ## What Changed
    
    - Updates `legacy_capture_cancellation_is_not_reported_as_timeout` to
    pass `workspace_roots_for(cwd.as_path()).as_slice()`, matching the
    adjacent capture test and the new runner signature.
    
    ## Verification
    
    - GitHub Actions CI is the important validation for this Windows-only
    compile path.
    - Created quickly to get Windows CI running while the separate Ubuntu
    `compact_resume_fork` timeout is still under investigation.
  • windows-sandbox: pass workspace roots to runner (#24108)
    ## Why
    
    #23813 switches the Windows sandbox runner path to `PermissionProfile`,
    but it still left one runtime anchor for resolving symbolic
    `:workspace_roots` entries. That is not enough once a turn has multiple
    effective workspace roots: exact entries and deny globs under
    `:workspace_roots` need to be materialized for every runtime root before
    the command runner chooses token mode or builds ACL plans.
    
    ## What Changed
    
    - Replaces the Windows runner/setup `permission_profile_cwd` plumbing
    with `workspace_roots: Vec<AbsolutePathBuf>`.
    - Resolves Windows-local `PermissionProfile` data with
    `materialize_project_roots_with_workspace_roots(...)` instead of the
    single-cwd helper.
    - Threads `Config::effective_workspace_roots()` through core execution,
    unified exec, TUI setup/read-grant flows, app-server setup, app-server
    `command/exec`, and `debug sandbox` on Windows.
    - Preserves those workspace roots through the zsh-fork escalation
    executor instead of rebuilding them from `sandbox_policy_cwd`.
    - Makes `ExecRequest::new(...)` and the remaining
    `build_exec_request(...)` helper path take
    `windows_sandbox_workspace_roots` explicitly so new call sites cannot
    silently fall back to `vec![cwd]`.
    - Clarifies the `debug sandbox` non-Windows comment: remaining
    cwd-dependent resolution still uses `sandbox_policy_cwd`, while
    `:workspace_roots` entries are already materialized from config roots.
    - Updates elevated runner IPC `SpawnRequest` to send `workspace_roots`
    and bumps the framed IPC protocol version to `3` for the payload shape
    change.
    - Adds Windows-local resolver coverage for expanding exact and glob
    `:workspace_roots` entries across multiple roots, plus core helper
    coverage proving explicit roots are preserved.
    
    ## Verification
    
    - `cargo check -p codex-windows-sandbox -p codex-core -p codex-tui -p
    codex-cli -p codex-app-server`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-core unix_escalation`
    - `cargo test -p codex-app-server windows_sandbox`
    - `cargo test -p codex-tui windows_sandbox`
    - `cargo test -p codex-cli debug_sandbox`
    - `just test -p codex-core unified_exec`
    - `just test -p codex-core
    build_exec_request_preserves_windows_workspace_roots`
    - `env -u CODEX_NETWORK_PROXY_ACTIVE -u
    CODEX_NETWORK_ALLOW_LOCAL_BINDING just test -p codex-app-server --lib
    command_exec`
    - `just test -p codex-windows-sandbox`
    - `just test -p codex-exec sandbox`
    - `just fix -p codex-core -p codex-app-server -p codex-windows-sandbox`
    
    A local macOS cross-check with `cargo check --target
    x86_64-pc-windows-msvc ...` did not reach crate Rust code because native
    dependencies require Windows SDK headers (`windows.h` / `assert.h`) in
    this environment; Windows CI remains the real target validation.
    
    Two local targeted filters compile but do not run assertions on macOS:
    `env -u CODEX_NETWORK_PROXY_ACTIVE -u CODEX_NETWORK_ALLOW_LOCAL_BINDING
    just test -p codex-app-server --lib command_exec_processor` matched zero
    tests, and `just test -p codex-linux-sandbox landlock` matched zero
    tests because the landlock suite is Linux-only.
  • Surface filesystem permission profiles in prompt context (#23924)
    ## Summary
    Some permission profiles can encode filesystem reads that should remain
    unavailable to the agent. Before this change, the model-visible context
    and automatic approval review prompt summarized the effective
    permissions as a legacy sandbox mode, which can omit permission-profile
    filesystem entries from escalation decisions.
    
    For example, a profile can grant workspace access while denying a
    private subtree across every workspace root:
    
    ```toml
    default_permissions = "restricted-workspace"
    
    [permissions.restricted-workspace.workspace_roots]
    "/Users/alice/project" = true
    "/Users/alice/other-project" = true
    
    [permissions.restricted-workspace.filesystem]
    ":minimal" = "read"
    
    [permissions.restricted-workspace.filesystem.":workspace_roots"]
    "." = "write"
    "private" = "deny"
    "private/**" = "deny"
    ```
    
    The context window now describes the workspace roots and effective
    filesystem side of the `PermissionProfile` directly, with deny entries
    marked as non-escalatable:
    
    ```xml
    <environment_context>
      <cwd>/Users/alice/project</cwd>
      <shell>zsh</shell>
      <filesystem><workspace_roots><root>/Users/alice/project</root><root>/Users/alice/other-project</root></workspace_roots><permission_profile type="managed"><file_system type="restricted"><entry access="read"><special>:minimal</special></entry><entry access="write"><path>/Users/alice/project</path></entry><entry access="write"><path>/Users/alice/other-project</path></entry><entry access="deny" escalatable="false"><path>/Users/alice/project/private</path></entry><entry access="deny" escalatable="false"><path>/Users/alice/other-project/private</path></entry><entry access="deny" escalatable="false"><glob>/Users/alice/project/private/**</glob></entry><entry access="deny" escalatable="false"><glob>/Users/alice/other-project/private/**</glob></entry></file_system></permission_profile></filesystem>
    </environment_context>
    ```
    
    Managed requirements can impose the same kind of deny-read restriction:
    
    ```toml
    [permissions.filesystem]
    deny_read = [
      "/Users/alice/project/private",
      "/Users/alice/project/private/**",
    ]
    ```
    
    The automatic approval review prompt also receives the parent turn's
    denied-read context, so review decisions can account for the active
    permission profile.
    
    ## What Changed
    - Render the effective filesystem profile in `<environment_context>`,
    including profile type, filesystem entries, workspace roots, and
    non-escalatable deny entries.
    - Persist effective `workspace_roots` in `TurnContextItem` so
    resumed/replayed context does not have to bind `:workspace_roots`
    through legacy `cwd` fallback.
    - Add explicit permission instructions that denied reads are policy
    restrictions, not escalation targets.
    - Pass the parent turn's denied-read context into automatic approval
    reviews.
    - Add targeted coverage for prompt rendering, workspace-root
    materialization, replay context, and review prompt context.
    - Keep the prompt-context test expectations platform-aware so the same
    filesystem rendering assertions pass on Unix and Windows paths.
    
    ## Testing
    - `just test -p codex-core
    context::environment_context::tests::serialize_environment_context_with_full_filesystem_profile`
    - `just test -p codex-core
    context::environment_context::tests::turn_context_item_filesystem_uses_workspace_roots_instead_of_cwd`
    - `just test -p codex-core
    context::permissions_instructions::permissions_instructions_tests::builds_permissions_from_profile_with_denied_reads`
    - `just fix -p codex-core`
    
    I also attempted `just test -p codex-core`; the changed prompt-context
    tests passed, but the full local run did not complete cleanly in this
    sandboxed macOS environment due unrelated user-shell `CODEX_SANDBOX*`
    expectations and integration-test timeouts.
  • [codex] Add user input client ids (#24653)
    ## Summary
    
    Adds an optional `clientId` field to app-server v2 `UserInput` and
    carries it through the core `UserInput` model so clients can correlate
    echoed user input items without relying on payload equality.
    
    ## Details
    
    - Adds `client_id: Option<String>` to core `UserInput` variants.
    - Exposes the v2 app-server field as `clientId` on the wire and in
    generated TypeScript.
    - Preserves the id when converting between app-server v2 and core
    protocol types.
    - Regenerates app-server schema fixtures.
    
    ## Validation
    
    - `just fmt`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-protocol`
    - `git diff --check`
  • fix(exec-server): reject websocket requests with Origin headers (#24947)
    ## Why
    
    `codex exec-server` has a local WebSocket listener, but it did not apply
    the same browser-origin request handling as the `app-server` WebSocket
    transport. Requests that carry an `Origin` header should not be upgraded
    by this local transport, keeping both local WebSocket servers consistent
    and avoiding unexpected browser-initiated connections.
    
    ## What changed
    
    - Added an Axum middleware guard in
    `codex-rs/exec-server/src/server/transport.rs` that returns `403
    Forbidden` for requests carrying an `Origin` header.
    - Added an integration test in `codex-rs/exec-server/tests/websocket.rs`
    that covers rejection of an `Origin`-bearing WebSocket handshake.
    - Kept ordinary WebSocket clients unchanged: existing no-`Origin`
    initialization and process behavior remains covered by the crate tests.
    
    ## Validation
    
    - `just test -p codex-exec-server` test phase (`186 passed`; run outside
    the parent macOS sandbox so nested sandbox tests can execute)
    - `just clippy -p codex-exec-server`
  • fix: cancel Windows sandbox on network denial (#19880)
    ## Why
    
    When Guardian or the sandbox network proxy detects and denies a network
    attempt, core cancels the associated execution through `ExecExpiration`.
    The Windows sandbox capture path was only forwarding the timeout
    component of that expiration state. As a result, a sandboxed Windows
    command whose network attempt had already been denied could keep running
    until its timeout elapsed rather than terminating promptly in response
    to the denial.
    
    This change closes that cancellation-propagation gap for Windows sandbox
    execution.
    
    ## What changed
    
    - Added `WindowsSandboxCancellationToken` as the cancellation hook
    exposed to Windows capture backends.
    - Extracted the cancellation token from `ExecExpiration` in core and
    passed it to both the direct and elevated Windows sandbox capture paths
    alongside the existing timeout.
    - Updated direct capture to poll for either process exit, timeout, or
    cancellation and to terminate cancelled processes without reporting them
    as timed out.
    - Updated elevated capture to watch for cancellation and send the
    existing `Terminate` IPC frame to the elevated runner. The watcher parks
    for 50 ms between checks to bound response latency without a tight busy
    wait.
    - Added Windows regression coverage for a long-running PowerShell
    command: cancellation ends capture before its timeout and does not set
    `timed_out`.
    - Added a visible skip diagnostic when that PowerShell-dependent
    regression test cannot execute, and consolidated the duplicated
    expiration-policy branch identified in review.
    
    ## Security
    
    This improves enforcement after a denied network attempt has been
    attributed to a Windows sandboxed execution: the command no longer
    remains alive simply because Windows capture lost the cancellation
    signal.
    
    This PR does not claim to make Windows offline mode an airtight
    no-network or no-exfiltration boundary. It does not introduce
    AppContainer or change how network denial is detected; it makes an
    already-detected denial promptly stop the affected sandboxed command.
    
    ## Validation
    
    ### Commands run
    
    - `just fmt`
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core network_denial`
    - `cargo clippy -p codex-core -p codex-windows-sandbox --tests --no-deps
    -- -D warnings`
    - `just argument-comment-lint -p codex-windows-sandbox -p codex-core`
    
    The new capture regression is `cfg(target_os = "windows")`, so Windows
    CI is the execution coverage for that test path. The local macOS test
    runs validate the host-runnable crate and core network-denial behavior.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • runtime: prepend zsh fork bin dir to PATH (#23768)
    ## Why
    
    #23756 makes packaged Codex builds include and default to the bundled
    zsh fork. The important reason to put that fork's directory at the front
    of `PATH` is to keep executable-level escalation working after a command
    leaves the original shell and later re-enters zsh through `env`.
    
    The expected chain is:
    
    1. The zsh fork runs the top-level shell command.
    2. That command launches another program, such as `python3`, while
    inheriting the `EXEC_WRAPPER` environment and the escalation socket fd.
    3. That program spawns a shell script whose shebang is `#!/usr/bin/env
    zsh` rather than `#!/bin/zsh`, and it does not close the escalation fd.
    4. `/usr/bin/env` resolves `zsh` through `PATH`, so it must find the
    packaged zsh fork before the system zsh.
    5. Commands inside that nested script are intercepted by the zsh fork
    and can still request escalation from Codex.
    
    If `PATH` resolves `zsh` to the system shell instead, the nested script
    loses zsh-fork exec interception. Commands that should request
    escalation can then run only in the original sandbox, or fail there,
    without Codex ever receiving the approval request.
    
    Shell snapshots make this slightly more subtle: a snapshot can restore
    an older `PATH` after the child shell starts. This PR treats the zsh
    fork `PATH` prepend as an explicit environment override so snapshot
    wrapping preserves it.
    
    ## What Changed
    
    - Added shared zsh-fork runtime helpers that prepend the configured zsh
    executable parent directory to `PATH` without duplicate entries.
    - Applied the zsh fork `PATH` prepend to both zsh-fork `shell_command`
    launches and unified-exec zsh-fork launches before sandbox command
    construction.
    - Kept the shell-command zsh-fork backend API narrow: it derives the
    configured zsh path from session services and rebuilds its sandbox
    environment from `req.env`, rather than accepting a second, competing
    environment map or a separately threaded bin dir.
    - Kept Unix-only zsh-fork `PATH` mutation out of Windows clippy-visible
    mutability.
    - Added coverage for duplicate `PATH` entries, for preserving the zsh
    fork prepend through shell snapshot wrapping, and for the nested
    `python3` -> `#!/usr/bin/env zsh` escalation flow.
    
    ## Testing
    
    - `just fmt`
    - `just fix -p codex-core`
    
    I left final test validation to CI after the latest review-comment
    cleanup. Before that cleanup, `just test -p codex-core zsh_fork` passed
    locally for the zsh-fork-focused tests.
  • [codex] Remove Bedrock OSS models from catalog (#24960)
    Remove the GPT OSS 120B and 20B entries from the Amazon Bedrock static
    model catalog, as they are no longer supported.
  • [codex] Handle PowerShell UTF-8 setup failures (#24949)
    Fixes #12496.
    
    ## Why
    
    Windows sandboxed PowerShell commands can run under
    `ConstrainedLanguage` on some machines, especially enterprise-managed
    Windows environments. In that mode, our PowerShell command prelude could
    fail before every command because it directly assigned
    `[Console]::OutputEncoding` to UTF-8. The actual user command still ran,
    but Codex surfaced noisy `Cannot set property. Property setting is
    supported only on core types in this language mode.` output for every
    shell call.
    
    ## What Changed
    
    - Makes the PowerShell UTF-8 output encoding prelude best-effort by
    wrapping the assignment in `try { ... } catch {}`.
    - Keeps the existing UTF-8 behavior when PowerShell allows the
    assignment.
    - Adds focused tests for adding the prelude and avoiding duplicate
    prelude insertion.
    
    ## Validation
    
    - `cargo fmt -p codex-shell-command`
    - `cargo check -p codex-shell-command`
    - `git diff --check`
    - Verified a local `ConstrainedLanguage` PowerShell probe prints only
    the command output with no property-setting error.
    - Verified `codex exec` from a temporary `chcp 437` context reports
    `utf-8` / `65001` and preserves non-ASCII output (`café`, `漢字`).
  • fix(tui): prevent repository-configured code execution in /diff (#24954)
    ## Why
    
    `/diff` is intended to display working-tree changes, but its Git
    invocations honored repository-selected executable helpers. A repository
    could configure diff/text conversion helpers, clean/process filters,
    `core.fsmonitor`, or `post-index-change` hooks that execute when a user
    runs `/diff`.
    
    Fixes
    [PSEC-4395](https://linear.app/openai/issue/PSEC-4395/codex-cli-diff-executes-repository-selected-diff-helpers).
    
    ## What Changed
    
    - Pass `--no-textconv` and `--no-ext-diff` for tracked and untracked
    diff generation.
    - Discover configured `filter.<driver>.clean` and `.process` entries,
    then neutralize the selected drivers through structured
    `GIT_CONFIG_KEY_*` / `GIT_CONFIG_VALUE_*` overrides, including driver
    names containing `=`.
    - Run all `/diff` Git probes with `core.fsmonitor=false` and a null
    `core.hooksPath`.
    - Use short submodule reporting while ignoring dirty submodule
    worktrees, since inspecting a checked-out submodule for dirtiness can
    execute filters from that child repository. This intentionally omits
    dirty-only submodule markers in order to preserve the non-executing
    security boundary.
    - Add real-Git marker tests covering filters, fsmonitor, hooks, and
    configured helpers inside checked-out submodules.
    
    ## How to Test
    
    1. In a repository with ordinary tracked and untracked edits, run
    `/diff`.
    2. Confirm the normal working-tree diff is shown for top-level files.
    3. Run the targeted tests below; they configure executable marker
    helpers for repository filters, fsmonitor, hooks, and a checked-out
    submodule, then verify `/diff` does not invoke them.
    4. Confirm a dirty-only submodule does not cause Codex to enter the
    submodule and execute its configured helper.
    
    Targeted tests:
    - `just test -p codex-tui get_git_diff_`
    
    Validation note: `just test -p codex-tui` runs the new coverage, but
    this worktree currently also has two unrelated failing guardian tests:
    `app::tests::update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default`
    and
    `app::tests::update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history`.
  • Add codex app-server --stdio alias (#24940)
    ## Summary
    - Add `--stdio` as a direct alias for `codex app-server --listen
    stdio://`.
    - Keep `--stdio` and `--listen` mutually exclusive.
    - Update the app-server README to document both forms.
  • Move Bazel Windows jobs onto codex-runners (#24952)
    The codex-windows runner group should be much faster than the default
    GHA runners. Since bazel jobs on windows are frequently the long pole
    for PRs checks, this will hopefully get people landing a bit faster.
  • Add feature-gated standalone image generation extension (#24723)
    ## Why
    
    Add a standalone image generation path that can be exercised
    independently of hosted Responses image generation, while retaining the
    hosted tool as fallback unless the extension is actually available to
    the model.
    
    ## What changed
    
    - Added the `codex-image-generation-extension` crate with standalone
    generate/edit execution, prior-image selection for edits, model-visible
    image output, and local generated-image persistence.
    - Installed the extension in app-server behind the disabled-by-default
    `imagegenext` feature and backend eligibility checks.
    - Updated core tool planning so eligible `image_gen.imagegen` exposure
    replaces hosted `image_generation`, while unavailable configurations
    retain hosted fallback.
    - Added coverage for extension behavior, edit history reuse, feature
    gating, auth eligibility, and hosted-tool replacement.
    - The extension is installed through app-server only in this PR; other
    execution paths retain hosted image generation because hosted
    replacement occurs only when the standalone executor is actually
    registered and model-visible.
    - The initial extension contract intentionally fixes the image model to
    `gpt-image-2` and uses automatic image parameters.
    - Native generated-image history/card parity and rollout persistence
    cleanup are intentionally deferred follow-up work.
    
    ## Validation
    
    - `just test -p codex-image-generation-extension`
    - `just test -p codex-features`
    - `just test -p codex-core
    hosted_tools_follow_provider_auth_model_and_config_gates`
    - `just test -p codex-app-server`
    - `just fix -p codex-image-generation-extension -p codex-features -p
    codex-core -p codex-app-server`
    - `just fmt`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Wire task completion into thread-idle lifecycle (#24928)
    ## Why
    
    #24744 introduced the thread idle lifecycle hook so idle continuation
    can be owned by lifecycle contributors instead of hard-coded goal
    runtime plumbing. Task completion still called
    `goal_runtime_apply(GoalRuntimeEvent::MaybeContinueIfIdle)` directly, so
    the post-turn idle transition remained goal-specific and did not notify
    generic thread lifecycle contributors.
    
    ## What Changed
    
    - Add `Session::emit_thread_idle_lifecycle_if_idle()` to gate idle
    emission on both no active turn and no queued trigger-turn mailbox work.
    - Call that helper when a task clears the active turn, replacing the
    direct `GoalRuntimeEvent::MaybeContinueIfIdle` path.
    - Cover the behavior with `codex-core` session tests for emitting after
    task completion and suppressing idle emission while trigger-turn mailbox
    work is pending.
    
    ## Verification
    
    - New tests in `core/src/session/tests.rs` exercise the idle lifecycle
    emission and trigger-turn mailbox guard.
  • TUI: Unified mentions tweaks + polish mentions rendering (#23363)
    This change keeps unified @mentions behind the mentions_v2 gate, moves
    the flag to under-development, and polishes mention rendering/history
    behavior.
    
    It also adds a few small improvements to the mentions feature around
    mention rendering and history round-tripping for plugin/tool mentions in
    message edit scenarios. Plugin selections now insert `@` mentions with
    better casing, and saved history preserves the visible sigil so recalled
    messages look the same as what the user typed.
    
    - Preserves `@` sigils when encoding/decoding mention history for
    tool/plugin paths.
    - Improves plugin mention insertion so display names/casing are
    reflected more cleanly in the composer.
    - Update composer to render user-entered plugin mentions in the same
    color as the mentions menu. ALso applies to recalled/edited messages.
    - Left/right arrows no longer switch unified-mention search modes after
    an @mention has already been accepted (Ex: arrowing left through a
    composed message that contains @mentions).
    - Keeps bound mentions stable around punctuation, so accepted `@`
    mentions do not reopen the popup and punctuated `$` mentions still
    persist to cross-session history.
    
    **Steps to test**
    - Ensure mentions_v2 is enabled through configuration or `--enable
    mentions_v2`
    - Type `@` in the TUI composer and verify filesystem/plugin/skill
    results are displayed in the unified mentions menu.
    - Select a plugin mention from the `@` popup and confirm the inserted
    text is an `@...` mention with casing, then recall/edit the message and
    confirm it still renders as `@...`.
    - Mention a skill and verify that skills still insert as `$skill`
    mentions rather than `@` mentions.
    - Verify punctuated mentions such as `@plugin.` and `($skill)` keep
    their bound mention behavior across editing and history recall.
  • chore: add GPT-5.5 to the Amazon Bedrock catalog (#24701)
    ## Summary
    
    Amazon Bedrock should expose GPT-5.5 alongside GPT-5.4, and the Bedrock
    GPT entries should stay aligned with the canonical bundled OpenAI model
    metadata instead of carrying a separate hand-written copy that can drift
    over time. This change will be merged when the model is online.
    
    This change:
    
    - Adds the Bedrock Mantle model id for `openai.gpt-5.5`.
    - Builds the Bedrock GPT-5.5 and GPT-5.4 catalog entries from the
    bundled OpenAI model catalog, then overrides the Bedrock-facing slug,
    explicit priority, and Bedrock-specific context windows.
    - Hardcodes both `context_window` and `max_context_window` to `272000`
    for Bedrock GPT-5.5 and GPT-5.4.
    - Keeps `openai.gpt-5.5` as the default Bedrock model ahead of
    `openai.gpt-5.4` and the Bedrock OSS models.
  • Fix extension turn item emitter test event ordering (#24936)
    ## Why
    
    PR #24813 added extension `TurnItemEmitter` coverage and introduced a
    test that records a conversation history item before asserting
    extension-emitted turn item events.
    
    `record_conversation_items()` also emits a `RawResponseItem` event to
    observers. The test was reading from the same event receiver and
    expected the next event to be `ItemStarted`, so the test failed reliably
    once the setup history item was present.
    
    ## What Changed
    
    Update
    `passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call` to
    consume and assert the expected setup `RawResponseItem` before checking
    the extension `ItemStarted`, `WebSearchBegin`, `ItemCompleted`, and
    `WebSearchEnd` events.
    
    This is test-only and does not change extension runtime behavior.
    
    ## Verification
    
    - `cargo nextest run --no-fail-fast -p codex-core
    tools::handlers::extension_tools::tests::passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call`
  • Reap stale multi-agent slots (#24903)
    ## Summary
    
    - Let `close_agent` clean up an agent that is still registered in
    `AgentRegistry` even when its underlying thread is already missing.
    - Preserve the explicit-close boundary: for known stale thread-spawn
    agents, mark the persisted spawn edge `Closed`, then treat
    `ThreadNotFound` / `InternalAgentDied` as a successful close so the
    registry slot can be released.
    - Add a regression for MultiAgentV2 task-name targets where
    `close_agent("worker")` succeeds after the worker thread has already
    disappeared.
    
    ## Motivation
    
    A worker can disappear from `ThreadManager` while its metadata still
    exists in the root `AgentRegistry`. Before this change, the close tool
    failed while trying to subscribe to the missing thread status, so it
    never reached the cleanup path that releases the registered agent slot.
    With `agents.max_threads = 1`, an explicit close of that stale task-name
    agent could fail and leave the session unable to spawn a replacement.
    
    ## Scope
    
    This PR intentionally does not add automatic stale-agent reaping to
    `spawn_agent`, `resume_agent`, or `list_agents`. A thread being missing
    from `ThreadManager` is not the same as an explicit close: persisted
    open spawn edges are still the durable source of truth for resume and
    task-name ownership until `close_agent` is called.
    
    ## Validation
    
    - `just test -p codex-core -E
    'test(multi_agent_v2_close_agent_reaps_stale_task_name_target) |
    test(resume_agent_from_rollout_reopens_open_descendants_after_manager_shutdown)'`
    - `just fix -p codex-core`
  • Expose MCP server info as part of server status (#24698)
    # Summary
    
    Expose MCP server info via App Server (when available) so apps can
    render a richer MCP experience
  • feat(app-server): include turns page on thread resume (#23534)
    ## Summary
    
    The client currently calls `thread/resume` to establish live updates and
    immediately follows it with `thread/turns/list` to hydrate recent turns.
    This lets `thread/resume` return that page directly, eliminating a round
    trip and the ordering/deduplication gap between the two calls.
    
    Experimental clients opt in with `initialTurnsPage: { limit,
    sortDirection, itemsView }`. The response returns `initialTurnsPage` as
    a `TurnsPage`, including cursors for paging further back in history.
    Keeping the controls in a nested opt-in object provides the useful
    `thread/turns/list` knobs without spreading page-specific parameters
    across `thread/resume`.
    
    ## Verification
    
    - `just fmt`
    - `just write-app-server-schema --experimental`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-app-server
    thread_resume_initial_turns_page_matches_requested_turns_list_page
    --tests`
    - `cargo test -p codex-app-server
    thread_resume_rejoins_running_thread_even_with_override_mismatch
    --tests`
    - `just fix -p codex-app-server-protocol -p codex-app-server`
  • extension-api: add TurnItemEmitter to tool calls (#24813)
    ## Why
    Extension-contributed tools need to emit visible turn items through
    Codex's normal event and persistence pipeline.
    
    ## What
    - Add `TurnItemEmitter` to extension `ToolCall`s and route the core
    implementation through `Session::emit_turn_item_*`.
    - Hold weak session and turn references so retained tool calls cannot
    keep host state alive.
    - Provide a no-op emitter for extension test callers.
    
    ## Test Plan
    - `just test -p codex-core -E
    'test(passes_turn_fields_and_scoped_turn_item_emitter_to_extension_call)'`
    
    ---------
    
    Co-authored-by: jif-oai <jif@openai.com>
  • Remove libubsan CI workaround (#24782)
    It seems that this was added to allow rustc to load proc macros that had
    been compiled with UBSan enabled, which zig does for debug and
    `ReleaseSafe` builds. When zig drives the link of the final binary it
    knows to include the ubsan runtime, but our zig-built artifacts are
    being linked into a binary whose linking rustc drives. This removes the
    libubsan workaround we have and replaces it with
    `-fno-sanitize=undefined` passed to zig.
    
    The new argument is passed at the end of zig's args so should take
    precedence over any earlier arguments from the script's caller.
  • Gate goal tools by thread eligibility (#24925)
    ## Why
    
    Goal tools create and update goal state for a persistent thread. The
    extension was only checking whether goals were enabled before
    advertising those tools, which meant they could be surfaced in contexts
    that should not receive thread goal controls: ephemeral threads without
    persistent thread state and review subagents.
    
    Those sessions can still run the goal extension lifecycle, but the
    thread tools should only be visible when the current thread can safely
    use them.
    
    ## What changed
    
    - Adds a `GoalRuntimeConfig` that separates goal enablement from whether
    goal tools are available for the current thread.
    - Computes tool eligibility on thread start from
    `persistent_thread_state_available` and `SessionSource`, hiding tools
    for review subagents.
    - Uses `GoalRuntimeHandle::tools_visible()` when contributing thread
    tools so enabled runtime state does not automatically imply tool
    exposure.
    - Adds backend coverage for hiding goal tools on ephemeral threads and
    review subagents.
    
    ## Testing
    
    - Added `goal_tools_hidden_for_ephemeral_threads`.
    - Added `goal_tools_hidden_for_review_subagents`.
  • Add app-server startup benchmark crate (#24651)
    ## Summary
    - Add a new `app-server-start-bench` crate to measure app-server startup
    performance
    - Wire the benchmark into the workspace and Bazel build so it can be run
    consistently
    - Update lockfiles and repo automation to account for the new package
  • [codex] Update OpenAI Docs skill (#24914)
    ## Summary
    - update the bundled `openai-docs` system skill to match the latest
    `openai-docs-plus` content from `skills-internal`
    - add the cached Codex manual fetch helper and expand the skill routing
    for Codex self-knowledge
    - keep the stable local skill identity and labels as `openai-docs`
    
    ## Why
    The built-in OpenAI Docs skill needed to reflect the current upstream
    guidance from `skills-internal` while preserving the local system-skill
    name used by Codex.
    
    ## Impact
    Codex now ships the newer OpenAI Docs skill behavior for Codex
    self-knowledge and manual-first documentation lookups.
    
    ## Validation
    - `just test -p codex-skills`
    - exact directory diff against transformed `skills-internal`
    `origin/main` was clean
  • Add turn error lifecycle contributor (#24916)
    Summary
    - Add TurnErrorInput and TurnLifecycleContributor::on_turn_error to the
    extension API.
    - Emit the turn-error lifecycle from core turn error paths, including
    usage limit failures.
    - Add direct lifecycle coverage for the emitted error facts and stores.
    
    Tests
    - just fmt
    - git diff --check
    - Not run: full tests or clippy (per instructions)
  • Add thread start contributor facts (#24915)
    Summary: add session source and persistent-state availability to
    ThreadStartInput; populate them from session init; update existing goal
    test harness constructors. Tests: just fmt; git diff --check. No full
    tests or clippy run per request.
  • [codex-cli] Refresh near-expiry ChatGPT access tokens before requests (#23546)
    ## Summary
    
    - refresh managed ChatGPT auth during auth resolution when its access
    token is inside ChatGPT web's five-minute near-expiry window
    - cover refresh-window decisions while preserving the existing
    expired-token refresh path
    
    ## Why
    
    Codex already resolves managed ChatGPT auth before outbound requests and
    refreshes expired access tokens there. This change adjusts the existing
    predicate to refresh a still-valid access token once it is within the
    same five-minute refresh window used by ChatGPT web, avoiding a request
    with a token about to expire.
    
    A cross-process serialization follow-up was explored in #24663 and
    closed for now; we do not currently suspect cross-process refresh races
    are a root cause of the refresh errors under investigation.
    
    External-token, API-key, and Agent Identity auth modes remain unchanged.
    
    ## Validation
    
    - `bazel test //codex-rs/login:login-all-test`
    - `just fmt` runs Rust formatting successfully, then its Python SDK Ruff
    step cannot install `openai-codex-cli-bin==0.131.0a4` on this Linux
    environment because no compatible wheel is published.
  • Add Guardian review metrics (#24897)
    ## Why
    
    Guardian reviews already emit analytics events, but we do not expose
    aggregate OpenTelemetry metrics for review volume, latency, token usage,
    or terminal outcomes. That makes it harder to monitor Guardian behavior
    during rollouts and to compare review outcomes by source, action type,
    session kind, model, and failure mode.
    
    ## What Changed
    
    - Added Guardian review metric names for count, total duration, time to
    first token, and token usage in `codex-rs/otel`.
    - Added `core/src/guardian/metrics.rs` to convert
    `GuardianReviewAnalyticsResult` into sanitized metric tags covering
    decision, terminal status, failure reason, approval request source,
    reviewed action, session kind, risk/outcome, model, reasoning effort,
    and context/truncation state.
    - Emitted the new metrics from `track_guardian_review` for each terminal
    Guardian review result.
    
    ## Testing
    
    - Added
    `guardian_review_metrics_record_counts_durations_and_token_usage`, which
    verifies the emitted count, duration, TTFT, token usage histograms, and
    tag set through the in-memory metrics exporter.
  • Fix memories namespace for Responses API tools (#24898)
    ## Why
    
    Dedicated memories tools are exposed through a Responses API namespace
    tool. The namespace itself has to be a valid tool identifier, so
    `memories/` can fail validation before the model ever gets a chance to
    call the memory tools.
    
    ## What changed
    
    - Changed `MEMORY_TOOLS_NAMESPACE` from `memories/` to `memories`.
    - Added `memory_tool_namespace_matches_responses_api_identifier` so the
    namespace stays non-empty and limited to Responses-safe identifier
    characters.
    
    ## Verification
    
    - Added unit coverage for the namespace identifier shape in
    `codex-rs/ext/memories/src/tests.rs`.
  • [codex] Fix Guardian argument comment lint (#24902)
    ## Summary
    - Add the required `/*parent_thread_id*/` argument comment at the
    Guardian review session test callsite flagged by CI.
    
    ## Validation
    - `just fmt`
    - Not run: clippy/tests, per request; CI will cover them.
  • Use stable Guardian prompt cache keys (#24803)
    ## Why
    
    Guardian review sessions are reusable across forks when their
    `GuardianReviewSessionReuseKey` is unchanged, but the underlying
    Responses request was still using the child thread ID as
    `prompt_cache_key`. That meant forked Guardian reviews that should share
    cache context produced different cache keys, reducing prompt cache reuse
    and weakening the reuse invariant.
    
    ## What Changed
    
    - Adds a `ModelClient` prompt cache key override and uses it for
    `ResponsesApiRequest.prompt_cache_key`.
    - Computes Guardian review cache keys as
    `guardian:<sha1(parent_thread_id:reuse_key)>`, scoped to the parent
    thread plus the reuse-sensitive Guardian config.
    - Wires session construction to apply that override only for Guardian
    sub-agent sessions.
    
    ## Testing
    
    - Added coverage that Guardian cache keys are stable for the same
    parent/reuse key, change when either the parent thread or reuse key
    changes, fit within the Responses API length limit, and are absent for
    non-Guardian sessions.
    - Extended the parallel review test to assert forked Guardian reviews
    send the same `prompt_cache_key`.
  • Thread Guardian cache key through session (#24895)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/session/session.rs. Validation was not run per
    request; this branch is expected to rely on the companion split PRs.
  • Assert Guardian prompt cache key reuse (#24894)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/tests.rs. Validation was not run per request;
    this branch is expected to rely on the companion split PRs.
  • Add Guardian review prompt cache key (#24893)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/review_session.rs. Validation was not run per
    request; this branch is expected to rely on the companion split PRs.
  • Export Guardian prompt cache key helper (#24892)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/guardian/mod.rs. Validation was not run per request;
    this branch is expected to rely on the companion split PRs.
  • Stabilize Guardian client cache key handling (#24891)
    Split from the Guardian prompt cache key change. This PR only updates
    codex-rs/core/src/client.rs. Validation was not run per request; this
    branch is expected to rely on the companion split PRs.
  • Move memories root setup out of core config (#24758)
    ## Why
    
    Config loading should not create or write-authorize the memories root
    just because memory support exists. Memory startup is the code path that
    actually materializes that tree.
    
    ## What
    
    - Stop creating the memories root during Config load and remove it from
    legacy workspace-write projections.
    - Grant the memories root read access only when the memories feature and
    use_memories are enabled.
    - Create the memories root inside memories startup before seeding
    extension instructions.
    - Update config and startup tests around the ownership boundary.
    
    ## Tests
    
    - just fmt
    - just fix -p codex-core
    - just fix -p codex-memories-write
    - just test -p codex-core
    memory_tool_makes_memories_root_readable_without_creating_or_widening_writes
    workspace_write_includes_configured_writable_root_once_without_memories_root
    permission_profile_override_keeps_memories_root_out_of_legacy_projection
    permissions_profiles_allow_direct_write_roots_outside_workspace_root
    default_permissions_profile_populates_runtime_sandbox_policy
    - just test -p codex-memories-write memories_startup_creates_memory_root
    
    Note: a broader just test -p codex-core run is not clean in this
    sandbox; it hit missing test_stdio_server plus seatbelt, realtime, and
    environment-sensitive failures. The changed config tests above pass.
  • [codex] Stage Python SDK beta versions from release tags (#24872)
    ## Summary
    - Treat `sdk/python` as a development template with source version
    `0.0.0-dev`, matching the existing Python runtime packaging pattern.
    - Have `python-v*` tags supply the published SDK beta version through
    the existing `stage-sdk --sdk-version` path.
    - Remove the workflow check requiring a source version bump for each
    beta release and remove its now-unused host Python setup step.
    - Keep the reviewed runtime dependency pin at
    `openai-codex-cli-bin==0.132.0`.
    - Remove beta-number-specific documentation so it does not need editing
    for each publish.
    
    ## Why
    The package staging script already writes the release version into the
    artifact. Requiring the checked-in SDK template version to match every
    tag adds release-only source churn without changing the package users
    receive.
    
    ## Validation
    - Not run locally; relying on online CI for this workflow and metadata
    change.
    
    ## Release
    After this PR lands, publish the next beta by pushing tag
    `python-v0.1.0b2` from merged `main`.
  • [codex] Remove Python SDK beta warning note (#24870)
    ## Summary
    - Remove the beta warning callout from the PyPI-facing Python SDK
    README.
    - Keep the existing Beta title and install/usage guidance unchanged.
    
    ## Validation
    - Not run locally; relying on online CI for this documentation-only
    change.
    
    ## Release
    - Land this change before publishing the next Python SDK beta.
  • [codex] Remove Python SDK language classifiers (#24868)
    ## Summary
    - Remove the Python language classifiers from the Python SDK package
    metadata.
    - Keep `requires-python = ">=3.10"` as the package's interpreter
    compatibility constraint.
    - Avoid presenting a curated version-support list in PyPI metadata.
    
    ## Validation
    - Not run locally; relying on online CI for this metadata-only change.
    
    ## Release
    - Land this change before publishing the next Python SDK beta.
  • [codex] Simplify Python SDK install guidance (#24866)
    ## Summary
    - Remove the exact-version install snippet from the PyPI-facing Python
    SDK README.
    - Remove the release-selection explanation so the install section
    presents the standard `pip install openai-codex` path directly.
    
    ## Validation
    - Not run locally; relying on online CI for this documentation-only
    change.
  • Treat refresh_token_reused 400s as relogin-required (#24830)
    ## Summary
    - classify known refresh-token terminal failures from `/oauth/token` as
    permanent even when the backend returns `400`
    - preserve the existing relogin-required message for
    `refresh_token_reused` instead of retrying and collapsing into a generic
    cloud requirements error
    - add regression coverage for `400 refresh_token_reused`
    
    ## Testing
    - `just fmt`
    - `cargo test -p codex-login`