Commit Graph

6525 Commits

  • Fix Windows sandbox clippy clones (#22687)
    ## Summary
    - remove two redundant `PathBuf` clones in Windows sandbox setup tests
    - fix current `rust-ci-full` Windows clippy failures on `main`
    
    ## Validation
    - `just fmt`
    - attempted on `dev`: `cargo clippy --target x86_64-pc-windows-msvc
    --tests --profile dev --timings -- -D warnings`
    - blocked by missing MSVC cross toolchain on the Linux devbox (`lib.exe`
    / MSVC C toolchain unavailable)
    - live failure evidence: main `rust-ci-full` runs 25880209898 and
    25879137967 failed on `windows-sandbox-rs/src/bin/setup_main/win.rs`
    with `clippy::redundant_clone` at the two edited callsites
  • Unqueue plugin list and read requests (#22703)
    ## Summary
    - remove the app-server `plugin-read` serialization queue from
    `plugin/list` and `plugin/read`
    - allow plugin read/list requests to start immediately instead of
    waiting behind other plugin read/list requests
    
    ## Test plan
    - `just fmt`
    - `cargo test -p codex-app-server-protocol`
  • make rust-release-prepare use env secret (#22702)
    made a `rust-release-prepare` environment with the necessary API key as
    an environment secret. use this in the workflow rather than the action
    secret.
    
    once this merges and i confirm it works as intended, ill rm the action
    secret.
  • [codex] Support multiple forced ChatGPT workspaces (#18161)
    ## Summary
    
    This change lets `forced_chatgpt_workspace_id` accept multiple workspace
    IDs instead of a single value.
    
    It keeps the existing config key name, adds backward-compatible parsing
    for a single string in `config.toml`, and normalizes the setting into an
    allowed workspace list across login enforcement, app-server config
    surfaces, and local ChatGPT auth helpers.
    
    ## Why
    
    Workspace-restricted deployments may need to allow more than one ChatGPT
    workspace without dropping the guardrail entirely.
    
    ## Server-side impact
    
    Codex's local server and app-server protocol needed changes because they
    previously assumed a single workspace ID. The local login flow now
    matches the auth backend interface by sending the allowed workspace list
    as a single comma-separated `allowed_workspace_id` query parameter.
    
    ## Validation
    
    This was tested with:
    
    - A single workspace config
    - With multi-workspace configs
    - With multiple workspaces in the config
    - The user only being a part of a subset of them
    
    All were successful.
    
    Automated coverage:
    
    - `cargo test -p codex-login`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-tui local_chatgpt_auth`
    - `cargo test --locked -p codex-app-server
    login_account_chatgpt_includes_forced_workspace_allowlist_query_param`
  • tests: isolate codex home for live cli (#22563)
    ## Why
    
    Some core integration-test paths were creating Codex state under ambient
    `~/.codex`. In environments where `HOME=/tmp`, that showed up as
    `/tmp/.codex`, which is host-level shared state and makes these tests
    environment/order sensitive.
    
    The affected paths were:
    
    - `core/tests/suite/live_cli.rs`: `run_live()` spawned the real CLI with
    a temp cwd, but without an isolated home, so the child resolved Codex
    home from ambient `HOME`.
    - core / exec-server integration test binaries using
    `configure_test_binary_dispatch(...)`: their startup ctor installs arg0
    helper aliases like `apply_patch` and `codex-linux-sandbox`. Full
    `arg0_dispatch()` also installs aliases from ambient Codex-home
    resolution, so test-binary startup could create `CODEX_HOME/tmp/arg0`;
    with `HOME=/tmp`, that became `/tmp/.codex/tmp/arg0/...`.
    
    ## What changed
    
    - `live_cli` now gives the spawned CLI a temp `HOME` and temp
    `CODEX_HOME`.
    - arg0 alias setup now has an explicit-home form,
    `prepend_path_entry_for_codex_aliases_in(...)`, so test helpers can
    place alias state under a temp directory without relying on ambient
    `CODEX_HOME`.
    - helper re-entry behavior is preserved with
    `dispatch_arg0_if_needed()`, so aliases like `apply_patch` and
    `codex-linux-sandbox` still dispatch correctly before test alias
    installation.
    - core test support keeps the temp Codex home alive for the lifetime of
    the test binary, matching the alias lifetime.
    
    ## Verification
    
    Verified on `dev2` with `HOME=/tmp` that the focused core test-binary
    startup path no longer recreates `/tmp/.codex`.
    
    Also checked the exact `live_cli` test path under `HOME=/tmp`; on `dev2`
    it still hits the existing remote-only `cargo_bin("codex-rs")`
    resolution failure before spawning the child, but `/tmp/.codex` remains
    absent after the run.
  • Fix remote environment test fixtures (#22572)
    ## Why
    The Docker remote-env coverage was failing before it reached the
    behavior those tests are meant to exercise. The remote-aware test
    fixture only registered the remote environment, so tests that
    intentionally select both `local` and `remote` could not start a turn.
    After that was fixed, two tests exposed stale fixtures: the approval
    test was auto-approving under workspace-write, and the remote
    `view_image` test was writing invalid PNG bytes.
    
    ## What Changed
    - Added `EnvironmentManager::create_for_tests_with_local(...)` so tests
    can keep the provider default while also selecting `local` explicitly.
    - Updated `build_remote_aware()` to use that test-only manager when a
    remote exec-server URL is present.
    - Changed the remote apply-patch approval helper to use
    `SandboxPolicy::new_read_only_policy()` so the test actually exercises
    approval caching per environment.
    - Replaced the hardcoded remote `view_image` PNG blob with the existing
    `png_bytes(...)` helper so the test uses a valid image fixture.
    
    ## Validation
    Ran these isolated Docker remote-env tests on the devbox with
    `$remote-tests` setup:
    -
    `suite::remote_env::apply_patch_freeform_routes_to_selected_remote_environment`
    -
    `suite::remote_env::apply_patch_approvals_are_remembered_per_environment`
    -
    `suite::remote_env::apply_patch_intercepted_exec_command_routes_to_selected_remote_environment`
    -
    `suite::remote_env::exec_command_routes_to_selected_remote_environment`
    - `suite::view_image::view_image_routes_to_selected_remote_environment`
    
    All five pass.
  • test: isolate exec review policy config test (#22512)
    ## Why
    
    
    `thread_start_params_include_review_policy_when_review_policy_is_manual_only`
    builds a `Config` with a temporary `CODEX_HOME`, but
    `ConfigBuilder::default()` can still load host-managed configuration. On
    local macOS machines with enterprise-managed Codex config, that host
    state can leak into the test and change the resulting config, even
    though CI does not have the same managed config source.
    
    This makes the test environment-dependent: it can pass in CI while
    failing locally for developers who have managed configuration installed.
    
    ## What Changed
    
    - Updated `codex-rs/exec/src/lib_tests.rs` so the test calls
    `LoaderOverrides::without_managed_config_for_tests()` through
    `ConfigBuilder::loader_overrides(...)`.
    - Left the rest of the test setup intact, including the temporary
    `CODEX_HOME`, temporary cwd, and explicit `approvals_reviewer` harness
    override.
    
    ## Verification
    
    ```shell
    cargo test -p codex-exec thread_start_params_include_review_policy_when_review_policy_is_manual_only
    ```
  • Support explicit MCP OAuth client IDs (#22575)
    ## Why
    Some MCP OAuth providers require a pre-registered public client ID and
    cannot rely on dynamic client registration. Codex already supports MCP
    OAuth, but it had no way to supply that client ID from config into the
    PKCE flow.
    
    ## What changed
    - add `oauth.client_id` under `[mcp_servers.<server>]` config, including
    config editing and schema generation
    - thread the configured client ID through CLI, app-server, plugin login,
    and MCP skill dependency OAuth entrypoints
    - configure RMCP authorization with the explicit client when present,
    while preserving the existing dynamic-registration path when it is
    absent
    - add focused coverage for config parsing/serialization and OAuth URL
    generation
    
    ## Verification
    - `cargo test -p codex-config -p codex-rmcp-client -p codex-mcp -p
    codex-core-plugins`
    - `cargo test -p codex-core blocking_replace_mcp_servers_round_trips
    --lib`
    - `cargo test -p codex-core
    replace_mcp_servers_streamable_http_serializes_oauth_resource --lib`
    - `cargo test -p codex-core config_schema_matches_fixture --lib`
    
    ## Notes
    Broader local package runs still hit unrelated pre-existing stack
    overflows in:
    - `codex-app-server::in_process_start_clamps_zero_channel_capacity`
    -
    `codex-core::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
  • [codex] fix plugin CLI active user layer compile (#22666)
    ## Why
    
    PR #21396 merged after #17141 removed the old
    `ConfigLayerStack::get_user_layer()` API. The new plugin CLI call sites
    still used that stale API, which caused `main` to fail compilation.
    
    ## What Changed
    
    - update `codex plugin marketplace list` to read configured marketplaces
    through `get_active_user_layer()`
    - update the plugin snapshot validation helper to use
    `get_active_user_layer()`
    
    This preserves the intended active writable user-layer behavior from the
    profile-aware config API while fixing the stale call sites.
    
    ## Validation
    
    - `cargo check -p codex-cli`
    - `cargo test -p codex-cli --test plugin_cli`
    - `git diff --check`
  • Prefer the model list fetched from the backend for SIWC users (#22547)
    ## Summary
    - For SIWC users, update the model list merging logic to prefer the
    model list fetched from the backend over the bundled model list (this is
    needed for special cases where users have a more limited set of models
    they're allowed to use)
    - Add or update tests covering the revised cache behavior
    
    ## Testing
    - Added/updated unit tests in
    `codex-rs/models-manager/src/manager_tests.rs`
    - Not run (not requested)
  • fix(tui): render network approval history by target (#22229)
    ## Why
    
    Network approval prompts are rendered without a command string on the
    app-server path. After the user approves one of those prompts, the TUI
    history cell previously fell back to command-oriented copy and produced
    malformed lines such as:
    
    ```text
    You approved codex to run  every time this session
    ```
    
    That hid the network target the user actually approved and left a
    visibly broken transcript entry.
    
    ## What changed
    
    - Preserve the approval subject as either a command or a network target
    when recording TUI approval decisions.
    - Render target-aware history copy for network approval outcomes:
      - approve once
      - approve for the current session
      - cancel
    - Include the approval protocol and preserve the managed-proxy
    `network-access` target when present, including non-default ports such
    as `https://example.com:8443`.
    - Fall back to formatting the network approval context as
    `protocol://host` when no generated target command is available.
    - Keep ordinary command approval history, Guardian approval history, and
    persisted network-rule history behavior unchanged.
    - Add focused regression coverage and snapshots for the three
    network-history cases.
    
    ## How to Test
    
    1. Start Codex in a flow that triggers a network approval prompt.
    2. Approve network access only for the current conversation.
    3. Confirm the transcript records the approved network target, for
    example:
    - `You approved codex network access to https://example.com:8443 every
    time this session`
    4. Trigger the prompt again and verify the one-time approval and cancel
    paths also record target-specific history text instead of an empty
    command gap.
    
    Targeted automated coverage:
    - `cargo test -p codex-tui network_exec_approval_history`
    
    ## Additional verification
    
    - `cargo insta pending-snapshots`
    - `git diff --check`
    - `just fix -p codex-tui`
    - `just argument-comment-lint`
    
    ## Known unrelated local test noise
    
    A full `cargo test -p codex-tui` run still hits a pre-existing stack
    overflow outside this change:
    - `tests::fork_last_filters_latest_session_by_cwd_unless_show_all`
    aborts with a stack overflow
  • [codex] Ignore fsmonitor config in Git metadata reads (#22652)
    ## Summary
    - keep Git metadata/status subprocesses independent of repository
    `core.fsmonitor` configuration
    - preserve existing working-tree state reporting while making the helper
    behavior more predictable
    - add regression coverage for `get_has_changes` when a repository
    defines an fsmonitor command
    
    ## Validation
    - `cargo fmt --all`
    - `cargo test -p codex-core test_get_has_changes_`
    - `cargo test -p codex-git-utils`
  • tests: avoid ambient temp sandbox roots (#22576)
    ## Why
    Some sandboxed integration tests enabled both ambient temp roots
    (`TMPDIR` and literal `/tmp`) even though they were not testing
    temp-root behavior. On Linux bwrap, making `/tmp` writable causes
    protected metadata mount targets such as `/tmp/.git`, `/tmp/.agents`,
    and `/tmp/.codex` to be synthesized. If a run is interrupted, those
    top-level markers can be left behind and contaminate later tests.
    
    ## What changed
    For the incidental integration tests that do not need ambient temp-root
    access, set `exclude_tmpdir_env_var` and `exclude_slash_tmp` to `true`.
    Dedicated protected-metadata coverage remains in the lower-level sandbox
    tests that use isolated temp roots.
    
    ## Verification
    Focused remote devbox repros passed with a watcher polling `/tmp/.git`,
    `/tmp/.agents`, and `/tmp/.codex`; no leaked markers were observed.
  • [codex] add plugin marketplace CLI commands (#21396)
    ## Why
    
    Plugin CLI installs should behave more like `apt-get install`:
    configured marketplaces are the only install sources, the local
    marketplace snapshot is the package index used at install time, and
    `plugins/cache` is only a cache of already-downloaded plugin bytes.
    
    That distinction matters once marketplaces and plugins have auth or
    availability state. A repo-local marketplace manifest or leftover cached
    plugin artifact should not silently become an install source unless the
    marketplace was explicitly configured and its readable snapshot still
    authorizes the plugin.
    
    ## What Changed
    
    - add CLI commands to list configured marketplaces and add, list, or
    remove marketplace plugins
    - accept stable `plugin@marketplace` ids for add/remove while preserving
    the explicit `--marketplace` form
    - restrict `codex plugin add` and `codex plugin list` to configured
    marketplaces instead of also discovering current-working-directory
    marketplace roots
    - fail `codex plugin add` and `codex plugin list` when a configured
    marketplace snapshot is missing or malformed instead of treating it as
    an empty source or a generic plugin miss
    - preserve marketplace snapshot semantics: a configured local/Git
    marketplace snapshot can authorize installs without consulting the
    original upstream source
    - allow `plugins/cache` reuse only after configured marketplace
    resolution succeeds
    - keep removal resilient after marketplace deletion or drift and ignore
    malformed marketplace config entries in listing
    
    ## Commands Added
    
    - `codex plugin add <plugin>@<marketplace>`
    - `codex plugin add <plugin> --marketplace <marketplace>`
    - `codex plugin list`
    - `codex plugin list --marketplace <marketplace>`
    - `codex plugin remove <plugin>@<marketplace>`
    - `codex plugin remove <plugin> --marketplace <marketplace>`
    - `codex plugin marketplace add <source>`
    - `codex plugin marketplace add <source> --ref <ref>`
    - `codex plugin marketplace add <source> --sparse <path>`
    - `codex plugin marketplace list`
    - `codex plugin marketplace upgrade`
    - `codex plugin marketplace upgrade <marketplace>`
    - `codex plugin marketplace remove <marketplace>`
    
    ## CLI Help Output
    
    <details>
    <summary><code>codex plugin --help</code></summary>
    
    ```text
    Manage Codex plugins
    
    Usage: codex plugin [OPTIONS] <COMMAND>
    
    Commands:
      add          Install a plugin from a configured marketplace snapshot
      list         List plugins available from configured marketplace snapshots
      marketplace  Add, list, upgrade, or remove configured plugin marketplaces
      remove       Remove an installed plugin from local config and cache
      help         Print this message or the help of the given subcommand(s)
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin add --help</code></summary>
    
    ```text
    Install a plugin from a configured marketplace snapshot.
    
    Pass either `PLUGIN@MARKETPLACE` or pass `PLUGIN` with `--marketplace MARKETPLACE`.
    
    Usage: codex plugin add [OPTIONS] <PLUGIN[@MARKETPLACE]>
    
    Arguments:
      <PLUGIN[@MARKETPLACE]>
              Plugin selector to install: either PLUGIN@MARKETPLACE or PLUGIN with --marketplace
    
    Options:
      -m, --marketplace <MARKETPLACE>
              Configured marketplace name to use when PLUGIN does not include @MARKETPLACE
    
    Examples:
      codex plugin add sample@debug
      codex plugin add sample --marketplace debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin list --help</code></summary>
    
    ```text
    List plugins available from configured marketplace snapshots
    
    Usage: codex plugin list [OPTIONS]
    
    Options:
      -m, --marketplace <MARKETPLACE>
              Only list plugins from this configured marketplace name
    
    Examples:
      codex plugin list
      codex plugin list --marketplace debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin remove --help</code></summary>
    
    ```text
    Remove an installed plugin from local config and cache.
    
    Pass either `PLUGIN@MARKETPLACE` or pass `PLUGIN` with `--marketplace MARKETPLACE`.
    
    Usage: codex plugin remove [OPTIONS] <PLUGIN[@MARKETPLACE]>
    
    Arguments:
      <PLUGIN[@MARKETPLACE]>
              Plugin selector to remove: either PLUGIN@MARKETPLACE or PLUGIN with --marketplace
    
    Options:
      -m, --marketplace <MARKETPLACE>
              Marketplace name to use when PLUGIN does not include @MARKETPLACE
    
    Examples:
      codex plugin remove sample@debug
      codex plugin remove sample --marketplace debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace --help</code></summary>
    
    ```text
    Add, list, upgrade, or remove configured plugin marketplaces
    
    Usage: codex plugin marketplace [OPTIONS] <COMMAND>
    
    Commands:
      add      Add a local or Git marketplace to the configured marketplace sources
      list     List configured marketplace names and their local snapshot roots
      upgrade  Refresh configured Git marketplace snapshots
      remove   Remove a configured marketplace source by name
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace add --help</code></summary>
    
    ```text
    Add a local or Git marketplace to the configured marketplace sources
    
    Usage: codex plugin marketplace add [OPTIONS] <SOURCE>
    
    Arguments:
      <SOURCE>
              Marketplace source: a local path, owner/repo[@ref], HTTPS Git URL, or SSH Git URL
    
    Options:
          --ref <REF>
              Git ref to fetch for Git marketplace sources
    
          --sparse <PATH>
              Sparse checkout path for Git marketplace sources. Can be repeated
    
    Examples:
      codex plugin marketplace add ./path/to/marketplace
      codex plugin marketplace add owner/repo --ref main
      codex plugin marketplace add https://github.com/owner/repo --sparse plugins/foo
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace list --help</code></summary>
    
    ```text
    List configured marketplace names and their local snapshot roots
    
    Usage: codex plugin marketplace list [OPTIONS]
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace upgrade --help</code></summary>
    
    ```text
    Refresh configured Git marketplace snapshots.
    
    Omit MARKETPLACE_NAME to upgrade all configured Git marketplaces.
    
    Usage: codex plugin marketplace upgrade [OPTIONS] [MARKETPLACE_NAME]
    
    Arguments:
      [MARKETPLACE_NAME]
              Optional configured marketplace name to upgrade. Omit to upgrade all Git marketplaces
    
    Examples:
      codex plugin marketplace upgrade
      codex plugin marketplace upgrade debug
    ```
    
    </details>
    
    <details>
    <summary><code>codex plugin marketplace remove --help</code></summary>
    
    ```text
    Remove a configured marketplace source by name
    
    Usage: codex plugin marketplace remove [OPTIONS] <MARKETPLACE_NAME>
    
    Arguments:
      <MARKETPLACE_NAME>
              Configured marketplace name to remove
    
    Example:
      codex plugin marketplace remove debug
    ```
    
    </details>
    
    ## Public Semantics
    
    - `codex plugin add <plugin>@<marketplace>` succeeds only when
    `<marketplace>` is configured and its local marketplace snapshot
    contains `<plugin>`
    - repo-local marketplaces are not install sources until the user runs
    `codex plugin marketplace add ...`
    - configured marketplace snapshots must be readable; missing or
    malformed snapshots fail the CLI operation rather than silently falling
    through to cache or empty results
    - cached plugin artifacts can satisfy reinstall only when the configured
    marketplace snapshot still authorizes that plugin
    - cached plugin artifacts alone never make a plugin installable
    
    ## Tests
    
    - `cargo test -p codex-cli --test plugin_cli`
    - `cargo clippy -p codex-cli --tests -- -D warnings`
    - `cargo test -p codex-cli`
    - `git diff --check`
    - `just bazel-lock-update`
    - `just bazel-lock-check`
  • tui: split composer attachment and popup state (#22581)
    ## Why
    
    `ChatComposer` currently owns text editing alongside attachment
    bookkeeping and popup lifecycle state, while `BottomPane` still triggers
    a couple of popup resyncs after composer methods that already do that
    work internally. That blurs the ownership boundary and makes the
    composer harder to simplify safely.
    
    This PR is part 1 of a two-part cleanup. It peels off the composer state
    that can move cleanly on its own, so the follow-up can tackle the
    heavier draft/editing boundary without mixing every concern into one
    diff.
    
    ## What changed
    
    - Move local and remote image bookkeeping, placeholder relabeling, and
    remote-image keyboard selection into `AttachmentState`.
    - Move active-popup and popup-dismissal/query bookkeeping into
    `PopupState`.
    - Update composer and history-search paths to use those state owners
    directly.
    - Remove redundant `BottomPane` popup synchronization after paste
    handling and `insert_str`.
    
    ## Part 2
    
    The follow-up PR will finish the cleanup around the remaining composer
    boundary: split out the draft/editing-oriented state and footer/status
    presentation concerns that still live in `ChatComposer`, then revisit
    the leftover `BottomPane` pass-throughs once those ownership lines are
    explicit. The goal is for `ChatComposer` to coordinate a few focused
    collaborators instead of continuing to be the landing zone for every
    input-path concern.
    
    ## Verification
    
    Did manual smoke tests.
  • Chore: better published unsigned artifacts (#22649)
    This is the exact same change as @bolinfest made but he could not push
    because of github action change permission.
    
    ## Why
    
    The `rust-release` workflow can now be run manually with
    `sign_macos=false` to skip macOS signing, but that path previously
    stopped before creating a GitHub Release. That left the unsigned macOS
    binaries available only as workflow-run artifacts, which are awkward to
    fetch from automation and cannot be retrieved with a simple
    unauthenticated `curl`.
    
    For the unsigned path we still should not perform the normal release
    side effects: no npm or Python publishing, no WinGet publishing, no
    `latest-alpha-cli` branch update, and no promotion to GitHub's latest
    release. The goal is only to make the build outputs easy to fetch from
    the release page.
    
    ## What changed
    
    - Allow the `release` job in `.github/workflows/rust-release.yml` to run
    for `workflow_dispatch` runs with `sign_macos=false`.
    - For unsigned runs, keep the unsigned macOS artifacts plus the normal
    Linux and Windows release artifacts needed for DotSlash, then
    create/update the GitHub Release with `make_latest: false`.
    - Keep the normal publish/promote paths gated to signed releases:
      - npm staging and publish
      - Python runtime publish
      - WinGet publish
      - `latest-alpha-cli` update
      - developer-site deploy
      - normal DotSlash release files
    - Add `.github/dotslash-unsigned-config.json`, which publishes
    `*-unsigned` DotSlash files that use unsigned macOS artifacts and the
    normal Linux/Windows artifacts.
    
    
    ## What I added
    PLEASE READ THIS!!!
    I added `codex-command-runner` and `codex-windows-sandbox-setup` entries
    to `.github/dotslash-unsigned-config.json` so that with
    `sign_macos=false` we would still get the dotslash files for those
    artifacts which are necessary for windows builds.
  • permissions: canonicalize workspace_roots and danger-full-access names (#22624)
    ## Why
    
    This is a small precursor to the larger permissions-migration work. Both
    the comparison stack in
    [#22401](https://github.com/openai/codex/pull/22401) /
    [#22402](https://github.com/openai/codex/pull/22402) and the alternate
    stack in [#22610](https://github.com/openai/codex/pull/22610) /
    [#22611](https://github.com/openai/codex/pull/22611) /
    [#22612](https://github.com/openai/codex/pull/22612) are easier to
    review if the terminology is already settled underneath them.
    
    Because `:project_roots` and `:danger-no-sandbox` have not shipped as
    stable user-facing surface area, carrying them forward as aliases would
    just add more migration logic to the later stacks. This PR removes that
    ambiguity now so the follow-on work can rely on one spelling for each
    built-in concept.
    
    ## What Changed
    
    - renamed the config-facing special filesystem key from `:project_roots`
    to `:workspace_roots`
    - dropped unpublished `:project_roots` parsing support in
    `core/src/config/permissions.rs`, so new config only recognizes
    `:workspace_roots`
    - renamed the built-in full-access permission profile id from
    `:danger-no-sandbox` to `:danger-full-access`
    - dropped unpublished `:danger-no-sandbox` support entirely, including
    the old active-profile canonicalization path, and added explicit
    rejection coverage for the legacy id
    - introduced shared built-in permission-profile id constants in
    `codex-rs/protocol/src/models.rs`
    - updated `core`, `app-server`, and `tui` call sites that special-case
    built-in profiles to use the shared constants and canonical ids
    - updated tests and the Linux sandbox README to use `:workspace_roots` /
    `:danger-full-access`
    
    ## Verification
    
    I focused verification on the three places this rename can regress:
    config parsing, active-profile identity surfaced back out of `core`, and
    user/server call sites that special-case built-in profiles.
    
    Targeted checks:
    
    -
    `config::tests::default_permissions_can_select_builtin_profile_without_permissions_table`
    -
    `config::tests::default_permissions_read_only_applies_additional_writable_roots_as_modifications`
    -
    `config::tests::default_permissions_can_select_builtin_full_access_profile`
    - `config::tests::legacy_danger_no_sandbox_is_rejected`
    - `workspace_root` filtered `codex-core` tests
    -
    `request_processors::thread_processor::thread_processor_tests::thread_processor_behavior_tests::requested_permissions_trust_project_uses_permission_profile_intent`
    -
    `suite::v2::turn_start::turn_start_rejects_invalid_permission_selection_before_starting_turn`
    - `status::tests::status_snapshot_shows_auto_review_permissions`
    -
    `status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access`
    -
    `app_server_session::tests::embedded_turn_permissions_use_active_profile_selection`
  • Fix turn extension data task plumbing (#22646)
    ## Summary
    - carry the per-turn extension data through RunningTask so abort
    handling can rebuild SessionTaskContext
    - update stale test ExtensionData::new() callsites to pass the turn id
    
    ## Testing
    - Not run after PR branch creation; CI will cover.
  • [codex] treat PowerShell stop-parsing forms as unsupported (#22643)
    ## Summary
    - Treat PowerShell stop-parsing token forms as unsupported in the
    AST-backed command flattener.
    - Add focused regressions at the parser layer and Windows command-safety
    layer.
    
    ## Why
    The command-safety parser lowers PowerShell AST elements into argv-like
    words. Stop-parsing syntax preserves a native-command argument shape
    that this lowering does not model, so these forms should stay on the
    conservative unsupported path.
    
    ## Validation
    - `cargo fmt --manifest-path codex-rs/Cargo.toml --all --check`
    - `cargo test --manifest-path codex-rs/Cargo.toml -p
    codex-shell-command`
  • feat: add layered --profile-v2 config files (#17141)
    ## Why
    
    `--profile-v2 <name>` gives launchers and runtime entry points a named
    profile config without making each profile duplicate the base user
    config. The base `$CODEX_HOME/config.toml` still loads first, then
    `$CODEX_HOME/<name>.config.toml` layers above it and becomes the active
    writable user config for that session.
    
    That keeps shared defaults, plugin/MCP setup, and managed/user
    constraints in one place while letting a named profile override only the
    pieces that need to differ.
    
    ## What Changed
    
    - Added the shared `--profile-v2 <name>` runtime option with validated
    plain names, now represented by `ProfileV2Name`.
    - Extended config layer state so the base user config and selected
    profile config are both `User` layers; APIs expose the active user layer
    and merged effective user config.
    - Threaded profile selection through runtime entry points: `codex`,
    `codex exec`, `codex review`, `codex resume`, `codex fork`, and `codex
    debug prompt-input`.
    - Made user-facing config writes go to the selected profile file when
    active, including TUI/settings persistence, app-server config writes,
    and MCP/app tool approval persistence.
    - Made plugin, marketplace, MCP, hooks, and config reload paths read
    from the merged user config so base and profile layers both participate.
    - Updated app-server config layer schemas to mark profile-backed user
    layers.
    
    ## Limits
    
    `--profile-v2` is still rejected for config-management subcommands such
    as feature, MCP, and marketplace edits. Those paths remain tied to the
    base `config.toml` until they have explicit profile-selection semantics.
    
    Some adjacent background writes may still update base or global state
    rather than the selected profile:
    
    - marketplace auto-upgrade metadata
    - automatic MCP dependency installs from skills
    - remote plugin sync or uninstall config edits
    - personality migration marker/default writes
    
    ## Verification
    
    Added targeted coverage for profile name validation, layer
    ordering/merging, selected-profile writes, app-server config writes,
    session hot reload, plugin config merging, hooks/config fixture updates,
    and MCP/app approval persistence.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Wire turn item contributors into stream output (#22494)
    ## Summary
    - run registered TurnItemContributor hooks for parsed stream output
    items
    - plumb the active turn extension store into stream item handling
    - preserve existing memory citation parsing as fallback after
    contributors run
    
    ## Tests
    - cargo test -p codex-core stream_events_utils -- --nocapture
    - just fmt
    - just fix -p codex-core
    - git diff --check
  • feat: make ToolExecutor an async trait (#22560)
    ## Why
    
    `codex_tools::ToolExecutor` keeps a tool spec attached to its runtime
    handler, but extension tools still carried a parallel
    `ExtensionToolFuture` / `ExtensionToolExecutor` shape. That made
    extension-owned tools look different from host tools even though
    routing, registration, and execution need the same abstraction.
    
    This PR makes the shared executor contract directly async and lets
    extension tools implement it too, so host tools and extension tools can
    move through the same registration path.
    
    ## What changed
    
    - Changed `ToolExecutor::handle` to an `async fn` using `async-trait`,
    and updated built-in tool handlers to implement the async trait
    directly.
    - Replaced the bespoke `ExtensionToolFuture` contract with a marker
    `ExtensionToolExecutor` over `ToolExecutor<ToolCall, Output =
    JsonToolOutput>`, re-exporting `ToolExecutor` from
    `codex-extension-api`.
    - Updated the memories extension tools to implement the shared executor
    trait.
    - Split tool-router construction into collected executors plus hosted
    model specs, keeping hosted tools like web search and image generation
    separate from executable handlers.
    - Updated spec/router tests and extension-tool stubs for the new
    executor shape.
    
    ## Verification
    
    - Not run locally.
  • Defer startup NUX impressions until startup succeeds (#22587)
    ## Why
    
    This is a follow-up to #22573. This problem was surfaced in a code
    review comment that I missed before merging the previous PR.
    
    Fresh-session startup could prepare a model-availability NUX before
    `app_server.start_thread(&config)` completed. If thread startup then
    failed, the TUI never rendered the tooltip, but
    `prepare_startup_tooltip_override(...)` had already persisted one of the
    limited impressions.
    
    ## What Changed
    
    - Move startup tooltip preparation inside the fresh-thread startup
    branch, after `start_thread(...)` succeeds.
    - Keep resume/fork paths unchanged.
    - Remove the now-redundant
    `should_prepare_startup_tooltip_override(...)` helper and its gate test.
  • Relax remote plugin sync gate (#22594)
    ## Summary
    - Allow remote installed-plugin cache refresh to start whenever plugins
    are enabled.
    - Allow remote installed-plugin bundle sync to start whenever plugins
    are enabled.
    - Remove the extra local `remote_plugin_enabled` guard from those
    background sync paths.
    
    ## Context
    Server-side installed plugin state and optional bundle URL behavior are
    owned by plugin-service `/public/plugins/installed`, so these local sync
    paths only need the overall plugin enablement gate.
    
    ## Test plan
    - `just fmt`
    - `cargo test -p codex-core-plugins`
  • Simplify TUI startup test coverage (#22573)
    ## Why
    
    The TUI startup test surface had drifted into expensive, brittle
    coverage:
    
    - `tui/tests/suite/no_panic_on_startup.rs` was already ignored as flaky
    while still spawning a PTY to exercise malformed exec-policy rules.
    - `tui/tests/suite/model_availability_nux.rs` used a seeded session,
    cursor-query spoofing, and repeated interrupts to verify a narrow
    resume-path invariant.
    - `app/tests.rs` had started accumulating unrelated startup and summary
    coverage in one flat module even after the surrounding app code was
    split into feature modules.
    
    This keeps those behaviors covered while making the tests cheaper to
    understand and less likely to rot. It also preserves the malformed-rules
    regression from #8803 without requiring a terminal orchestration test.
    
    ## What changed
    
    - Replaced the malformed `rules` startup PTY case with a direct
    exec-policy loader regression:
    
    [`rules_path_file_returns_read_dir_error`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/core/src/exec_policy_tests.rs#L264-L284)
    - Made the existing fresh-session-only startup tooltip behavior explicit
    with
    
    [`should_prepare_startup_tooltip_override`](https://github.com/openai/codex/blob/21b6b5622f18b8cac0ea41fd083b3106778d9ffc/codex-rs/tui/src/app/thread_routing.rs#L1272-L1279),
    then added focused coverage for the resume/fork gate and the persisted
    NUX counter.
    - Split startup and session-summary coverage out of
    `tui/src/app/tests.rs` into dedicated modules so the test layout better
    mirrors the current app architecture.
    - Converted one single-message goal validation snapshot into semantic
    assertions where layout was not the behavior under test.
    - Removed the two PTY-heavy suite files that the narrower tests now
    supersede.
    
    ## Verification
    
    - `cargo test -p codex-core rules_path_file_returns_read_dir_error`
    - `cargo test -p codex-tui startup_`
    - `cargo test -p codex-tui session_summary_`
    - `cargo test -p codex-tui
    goal_slash_command_rejects_oversized_objective`
  • enable/disable remote control at runtime, not via features (#22578)
    ## Why
    reapplies https://github.com/openai/codex/pull/22386 which was
    previously reverted
    
    Also, introduce `remoteControl/enable` and `remoteControl/disable`
    app-server APIs to toggle on/off remote control at runtime for a given
    running app-server instance.
    
    ## What Changed
    
    - Adds experimental v2 RPCs:
      - `remoteControl/enable`
      - `remoteControl/disable`
    - Adds `RemoteControlRequestProcessor` and routes the new RPCs through
    it instead of `ConfigRequestProcessor`.
    - Adds named `RemoteControlHandle::enable`, `disable`, and `status`
    methods.
    - Makes `remoteControl/enable` return an error when sqlite state DB is
    unavailable, while keeping enrollment/websocket failures as async status
    updates.
    - Adds `AppServerRuntimeOptions.remote_control_enabled` and hidden
    `--remote-control` flags for `codex app-server` and `codex-app-server`.
    - Updates managed daemon startup to use `codex app-server
    --remote-control --listen unix://`.
    - Marks `Feature::RemoteControl` as removed and ignores
    `[features].remote_control`.
    - Updates app-server README entries for the new remote-control methods.
  • Improve remote-control daemon UX (#22562)
    ## Why
    
    `codex remote-control` manages the app-server daemon with
    `remote_control` enabled, but it previously only exposed an implicit
    start path. Once started, there was no obvious top-level
    `remote-control` command for stopping the daemon; users had to know
    about the lower-level `codex app-server daemon stop` command.
    
    The startup failure for missing managed installs was also ambiguous.
    `codex remote-control` and daemon bootstrap require the standalone Codex
    install under `CODEX_HOME/packages/standalone/current/codex`, but the
    old error only said to install Codex first, which is unclear when
    another `codex` binary is already on PATH. Now we add an explicit
    instruction for how to get the standalone Codex install.
    
    ## What changed
    
    - Converts `codex remote-control` into a command group while preserving
    bare `codex remote-control` as the existing start behavior.
    - Adds `codex remote-control start` as the explicit start path.
    - Adds `codex remote-control stop`, which maps to app-server daemon
    stop.
    - Updates the shared daemon managed-install error to name the missing
    standalone path, explain why that install is required, provide the
    installer command, and tell users to rerun the command they just tried.
    
    ## Verification
    
    - `cargo test -p codex-app-server-daemon`
    - `cargo test -p codex-cli`
    - `./target/debug/codex remote-control --help`
  • chore(config) rm experimental_use_freeform_apply_patch (#22565)
    ## Summary
    Get rid of the `experimental_use_freeform_apply_patch` config option,
    since it is now encoded in model config. No deprecation message since it
    has been experimental this entire time.
    
    ## Testing
    - [x] Updated unit tests
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: Block appserver startup if state db can't be opened (#22580)
    All apps must be able to open the db to proceed -- codex is having
    issues with manufacturing new installation ids in local mode when the db
    can't be opened for race conditions or any other reasons.
  • Remove connector_openai prefix filtering (#22555)
    Remove unnecessary prefix filtering from codex
    
    ## Test Plan
    
    Test local cli build + make sure backend returns appropriate apps 
    
    ```
    cd ~/code/codex/codex-rs
    cargo build -p codex-cli --bin codex
    ./target/debug/codex
    ```
    
    Appropriate apps show up in my list
  • Add unsigned macOS release artifacts (#22559)
    ## Summary
    - Upload unsigned macOS release binaries before signing so they remain
    available from the workflow run if signing fails
    - Add a manual `workflow_dispatch` option, `sign_macos`, defaulting to
    `true`
    - When `sign_macos=false`, skip macOS signing, signed-name macOS
    artifacts, DMGs, npm/DotSlash/PyPI publishing, latest release marking,
    and `latest-alpha-cli` updates
    
    
    ## Process
    HAVE NOT TESTED YET BUT we should be able to run
    ```
    gh workflow run rust-release.yml \
      -R openai/codex \
      --ref rust-v0.132.0 \
      -f sign_macos=false
    ```
    
    which will then start the rust-release script with `sign_macos` and
    therefore do not codesign mac and also no release afterward.
  • [codex] Canonicalize shared workspace plugin IDs (#22564)
    ## Summary
    - Canonicalize private and unlisted workspace shared plugin IDs to
    `workspace-shared-with-me`.
    - Keep `plugin/list` private/unlisted shared-with-me buckets as UI
    grouping only.
    - Update share read/list/checkout and cache cleanup coverage for the
    canonical namespace.
    
    ## Tests
    - `cargo test -p codex-app-server --test all
    plugin_list_fetches_shared_with_me_kind`
    - `cargo test -p codex-app-server --test all
    plugin_read_returns_share_context_for_shared_remote_plugin`
    - `cargo test -p codex-app-server --test all suite::v2::plugin_share`
    - `cargo test -p codex-core-plugins
    list_remote_plugin_shares_fetches_created_workspace_plugins`
    - `cargo test -p codex-core-plugins
    stale_remote_plugin_cleanup_removes_old_shared_with_me_cache_and_keeps_canonical_cache`
    - `git diff --check`
  • Refactor chatwidget orchestration into modules (phase 5) (#22537)
    ## Why
    
    `chatwidget.rs` is still carrying too many unrelated responsibilities in
    one file. #22269 started a five-phase cleanup to move coherent behavior
    domains into focused modules while keeping `chatwidget.rs` as the
    composition layer. #22407 completed phase 2 by extracting input and
    submission flow, #22433 completed phase 3 by extracting protocol,
    replay, streaming, and tool lifecycle handling, and #22518 completed
    phase 4 by extracting settings, popups, and status surfaces.
    
    This PR is phase 5. It cleans up the remaining constructor and
    orchestration code now that the larger behavior domains have moved out,
    leaving `chatwidget.rs` much closer to the composition layer the cleanup
    was aiming for. This is once again a mechanical movement of existing
    functions. No functional changes.
    
    ## What Changed
    
    - Added focused modules for widget construction and initial wiring,
    session configuration flow, key/composer interaction routing, review
    popup orchestration, desktop notification coalescing, and render
    composition.
    - Moved the remaining constructor, session setup, interaction,
    notification, review picker, and rendering helpers out of
    `codex-rs/tui/src/chatwidget.rs`.
    - Preserved the existing startup/session behavior, keyboard handling,
    review picker flow, notification priority behavior, and render
    composition while shrinking the central widget module substantially.
    - Left `codex-rs/tui/src/chatwidget.rs` as the registration and
    composition surface for the extracted behavior modules.
    
    ## Cleanup Phases
    
    The five-phase cleanup plan from #22269 is:
    
    1. Phase 1: mechanical helper and state moves. Completed in #22269.
    2. Phase 2: extract input and submission flow, including queued user
    messages, shell prompt submission, pending steer restoration, and thread
    input snapshot/restore behavior. Completed in #22407.
    3. Phase 3: extract protocol, replay, streaming, and tool lifecycle
    handling, while preserving active-cell grouping, transcript
    invalidation, interrupt deferral, and final-message separator behavior.
    Completed in #22433.
    4. Phase 4: extract settings, popups, and status surfaces, including
    model/reasoning/collaboration/personality popups, permission prompts,
    rate-limit UI, and connectors helpers. Completed in #22518.
    5. Phase 5: clean up the remaining constructor and orchestration code
    once the larger behavior domains have moved out, leaving `chatwidget.rs`
    as the composition layer. This PR.
    
    ## Verification
    
    - `cargo check -p codex-tui`
    - `cargo test -p codex-tui chatwidget::tests::popups_and_settings`
    - `cargo test -p codex-tui chatwidget::tests::plan_mode`
    - `cargo test -p codex-tui chatwidget::tests::review_mode`
    - `cargo test -p codex-tui chatwidget::tests::status_and_layout`
    
    `cargo test -p codex-tui` also compiles and begins running, but aborts
    in the unchanged app-side test
    `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails`
    with the same reproducible stack overflow noted in phase 4.
  • Remove resurrected /collab slash command (#22535)
    ## Summary
    `/collab` was intentionally removed in
    [#12012](https://github.com/openai/codex/pull/12012), but the
    TUI/app-server migration accidentally brought that slash-command path
    back. This restores the earlier product decision so the TUI no longer
    advertises or dispatches `/collab`. This command was redundant because
    it did the same thing as `/plan` but in a less-intuitive way.
    
    ## What Changed
    - Remove `SlashCommand::Collab` from the TUI slash-command surface.
    - Delete the picker and app-event plumbing that only existed to service
    `/collab`.
    - Remove obsolete TUI test coverage for the deleted picker flow.
  • Spill oversized PreToolUse additionalContext (#22529)
    # Why
    
    `PreToolUse.additionalContext` became model-visible after #20692, but
    the hook-output spilling path from #21069 never picked up that newer
    lane. As a result, oversized `PreToolUse` context could bypass the
    truncation/spill treatment that already applies to the other hook
    outputs Codex forwards to the model.
    
    # What
    
    - Run `PreToolUseOutcome.additional_contexts` through
    `maybe_spill_texts(...)`
    - Add an integration test proving a large `PreToolUse.additionalContext`
    is replaced with a truncated preview plus spill-file pointer, while the
    full text is preserved on disk.
  • Make multi_agent_v2 wait_agent timeouts configurable (#22528)
    ## Why
    
    `multi_agent_v2` already allowed configuring the minimum `wait_agent`
    timeout, but the default timeout and upper bound were still hard-coded.
    That made it hard to tune waits for subagent mailbox activity in
    sessions that need either faster wakeups or longer waits, and it meant
    the model-visible `wait_agent` schema could not fully reflect the
    resolved runtime limits.
    
    ## What Changed
    
    - Added `features.multi_agent_v2.max_wait_timeout_ms` and
    `features.multi_agent_v2.default_wait_timeout_ms` alongside the existing
    `min_wait_timeout_ms` setting.
    - Validated all three timeouts in config as `0..=3_600_000`, with
    `min_wait_timeout_ms <= default_wait_timeout_ms <= max_wait_timeout_ms`.
    - Thread and review session tool config now passes the resolved
    min/default/max values into the `wait_agent` tool schema.
    - `wait_agent` now uses the configured default when `timeout_ms` is
    omitted and rejects explicit values outside the configured min/max range
    instead of silently clamping them.
    - Updated the generated config schema and config-lock test coverage for
    the new fields.
  • Avoid PowerShell profiles in elevated Windows sandbox (#21400)
    ## Why
    
    On Windows, elevated sandboxed commands run under a dedicated sandbox
    account while `HOME` / `USERPROFILE` can still point at the real user's
    profile directory. For PowerShell login shells, that combination can
    make the sandbox account try to load the real user's PowerShell profile
    script. If the sandbox account's execution policy differs from the real
    user's policy, startup can emit profile-loading errors before the
    requested command runs.
    
    For this backend, loading the profile is not a faithful user login
    shell: it is cross-account profile execution. Treating these PowerShell
    invocations as non-login shells avoids that invalid startup path.
    
    ## Why This Happens Late
    
    The normal `login` decision is resolved when shell argv is created, but
    that point is too early to make this Windows sandbox-specific decision.
    At argv creation time we do not yet know the actual sandbox attempt that
    will run the command. A turn can include sandboxed and unsandboxed
    attempts, and a broad turn-level override would also affect Full Access
    commands where the user's profile should remain available.
    
    Instead, this change carries the selected `ShellType` alongside the argv
    and applies the `-NoProfile` adjustment in the shell runtimes once the
    `SandboxAttempt` is known. That keeps the override scoped to actual
    `WindowsRestrictedToken` attempts with `WindowsSandboxLevel::Elevated`.
    
    The runtime uses the selected shell metadata rather than re-detecting
    PowerShell from argv. That avoids brittle parsing and covers PowerShell
    invocation shapes such as `-EncodedCommand`.
    
    ## What Changed
    
    - Carry selected shell metadata through `exec_command` / unified exec
    requests and shell tool requests.
    - Insert `-NoProfile` for PowerShell commands only when the runtime is
    about to execute a sandboxed elevated Windows attempt.
    - Add focused unit coverage for elevated Windows PowerShell,
    `-EncodedCommand`, existing `-NoProfile`, legacy restricted-token
    attempts, unsandboxed attempts, and non-PowerShell commands.
    
    ## Verification
    
    - `cargo test -p codex-core disable_powershell_profile_tests`
    - `cargo test -p codex-core test_get_command`
    - `cargo clippy --fix --tests --allow-dirty --allow-no-vcs -p
    codex-core`
    
    A full `cargo test -p codex-core` run was also attempted during
    development, but it still hit an unrelated stack overflow in
    `agent::control` tests before reaching this area.
  • clean up instructions (#22543)
    rm behavioral steering in tool docs for code mode.
  • feat(cli): add codex doctor diagnostics (#22336)
    ## Why
    
    Users and support need a single command that captures the local Codex
    runtime, configuration, auth, terminal, network, and state shape without
    asking the user to know which diagnostic depth to choose first. `codex
    doctor` now runs the useful checks by default and makes the detailed
    human output the default because the command is usually run when someone
    already needs context.
    
    The command also targets concrete support failure modes we have seen
    while iterating on the design:
    
    - update-target mismatches like #21956, where the installed package
    manager target can differ from the running executable
    - terminal and multiplexer issues that depend on `TERM`, tmux/zellij
    state, color handling, and TTY metadata
    - provider-specific HTTP/WebSocket connectivity, including ChatGPT
    WebSocket handshakes and API-key/provider endpoint reachability
    - local state/log SQLite integrity problems and large rollout
    directories
    - feedback reports that need an attached, redacted diagnostic snapshot
    without asking the user to run a second command
    
    ## What Changed
    
    - Adds `codex doctor` as a grouped CLI diagnostic report with default
    detailed output and `--summary` for the compact view.
    - Adds stable report sections for Environment, Configuration, Updates,
    Connectivity, and Background Server, plus a top Notes block that
    promotes anomalies such as available updates, large rollout directories,
    optional MCP issues, and mixed auth signals.
    - Adds runtime provenance, install consistency, bundled/system search
    readiness, terminal/multiplexer metadata, `config.toml` parse status,
    auth mode details, sandbox details, feature flag summaries, update
    cache/latest-version state, app-server daemon state, SQLite integrity
    checks, rollout statistics, and provider-aware network diagnostics.
    - Adds ChatGPT WebSocket diagnostics that report the negotiated HTTP
    upgrade as `HTTP 101 Switching Protocols` and include timeout, DNS,
    auth, and provider context in detailed output.
    - Makes reachability provider-aware: API-key OpenAI setups check the API
    endpoint, ChatGPT auth checks the ChatGPT path, and custom/AWS/local
    providers check configured HTTP endpoints when available.
    - Adds structured, redacted JSON output where `checks` is keyed by check
    id and `details` is a key/value object for support tooling.
    - Integrates doctor with feedback uploads by attaching a best-effort
    `codex-doctor-report.json` report and adding derived Sentry tags for
    overall status and failing/warning checks.
    - Updates the TUI feedback consent copy so users can see that the doctor
    report is included when logs/diagnostics are uploaded.
    - Updates the CLI bug issue template to ask reporters for `codex doctor
    --json` and render pasted reports as JSON.
    
    ## Example Output
    
    The examples below are sanitized from local smoke runs with `--no-color`
    so the structure is reviewable in plain text.
    
    ### `codex doctor`
    
    ```text
    Codex Doctor v0.0.0 · macos-aarch64
    
    Notes
       ↑ updates      0.130.0 available (current 0.0.0, dismissed 0.128.0)
       ⚠ rollouts     1,526 active files · 2.53 GB on disk
       ⚠ mcp          MCP configuration has optional issues
       ⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
    ─────────────────────────────────────────────────────────────
    
    Environment
      ✓ runtime      local debug build
          version                  0.0.0
          install method           other
          commit                   unknown
          executable               ~/code/codex.fcoury-doct…x-rs/target/debug/codex
      ✓ install      consistent
          context                  other
          managed by               npm: no · bun: no · package root —
          PATH entries (2)         ~/.local/share/mise/installs/node/24/bin/codex
                                   ~/.local/share/mise/shims/codex
      ✓ search       ripgrep 15.1.0 (system, `rg`)
      ✓ terminal     Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
          terminal                 Ghostty
          TERM_PROGRAM             ghostty
          terminal version         1.3.2-main-+b0f827665
          TERM                     xterm-256color
          multiplexer              tmux 3.6a
          tmux extended-keys       on
          tmux allow-passthrough   on
          tmux set-clipboard       on
      ✓ state        databases healthy
          CODEX_HOME               ~/.codex (dir)
          state DB                 ~/.codex/state_5.sqlite (file) · integrity ok
          log DB                   ~/.codex/logs_2.sqlite (file) · integrity ok
          active rollouts          1,526 files · 2.53 GB (avg 1.70 MB)
          archived rollouts        8 files · 3.84 MB (avg 491.11 KB)
    
    Configuration
      ✓ config       loaded
          model                    gpt-5.5 · openai
          cwd                      ~/code/codex.fcoury-doctor/codex-rs
          config.toml              ~/.codex/config.toml
          config.toml parse        ok
          MCP servers              1
          feature flags            36 enabled · 7 overridden (full list with --all)
          overrides                code_mode, code_mode_only, memories, chronicle, goals, remote_control, prevent_idle_sleep
      ✓ auth         auth is configured
          auth storage mode        File
          auth file                ~/.codex/auth.json
          auth env vars present    OPENAI_API_KEY
          stored auth mode         chatgpt
          stored API key           false
          stored ChatGPT tokens    true
          stored agent identity    false
      ⚠ mcp          MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
          configured servers       1
          disabled servers         0
          streamable_http servers  1
          optional reachability    openaiDeveloperDocs: https://developers.openai.com/mcp (HEAD connect failed; GET connect failed)
      ✓ sandbox      restricted fs + restricted network · approval OnRequest
          approval policy          OnRequest
          filesystem sandbox       restricted
          network sandbox          restricted
    
    Connectivity
      ✓ network      network-related environment looks readable
      ✓ websocket    connected (HTTP 101 Switching Protocols) · 15s timeout
          model provider           openai
          provider name            OpenAI
          wire API                 responses
          supports websockets      true
          connect timeout          15000 ms
          auth mode                chatgpt
          endpoint                 wss://chatgpt.com/backend-api/<redacted>
          DNS                      2 IPv4, 2 IPv6, first IPv6
          handshake result         HTTP 101 Switching Protocols
      ✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
          reachability mode        API key auth
          openai API               https://api.openai.com/v1 connect failed (required)
    
    Background Server
      ○ app-server   not running (ephemeral mode)
    
    ─────────────────────────────────────────────────────────────
    11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
    
    --summary compact output           --all expand truncated lists
    --json redacted report
    ```
    
    ### `codex doctor --summary`
    
    ```text
    Codex Doctor v0.0.0 · macos-aarch64
    
    Notes
       ↑ updates      0.130.0 available (current 0.0.0, dismissed 0.128.0)
       ⚠ rollouts     1,526 active files · 2.53 GB on disk
       ⚠ mcp          MCP configuration has optional issues
       ⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode
    ─────────────────────────────────────────────────────────────
    
    Environment
      ✓ runtime      local debug build
      ✓ install      consistent
      ✓ search       ripgrep 15.1.0 (system, `rg`)
      ✓ terminal     Ghostty 1.3.2-main-+b0f827665 · tmux 3.6a · TERM=xterm-256color
      ✓ state        databases healthy
    
    Configuration
      ✓ config       loaded
      ✓ auth         auth is configured
      ⚠ mcp          MCP configuration has optional issues — Set the missing MCP env vars or disable the affected server.
      ✓ sandbox      restricted fs + restricted network · approval OnRequest
    
    Updates
      ✓ updates      update configuration is locally consistent
    
    Connectivity
      ✓ network      network-related environment looks readable
      ✓ websocket    connected (HTTP 101 Switching Protocols) · 15s timeout
      ✗ reachability one or more required provider endpoints are unreachable over HTTP — Check proxy, VPN, firewall, DNS, and custom CA configuration.
    
    Background Server
      ○ app-server   not running (ephemeral mode)
    
    ─────────────────────────────────────────────────────────────
    11 ok · 1 idle · 4 notes · 1 warn · 1 fail failed
    
    Run codex doctor without --summary for detailed diagnostics.
    --all expand truncated lists       --json redacted report
    ```
    
    ### `codex doctor --json` shape
    
    ```json
    {
      "schema_version": 1,
      "overall_status": "fail",
      "checks": {
        "runtime.provenance": {
          "id": "runtime.provenance",
          "category": "Environment",
          "status": "ok",
          "summary": "local debug build",
          "details": {
            "version": "0.0.0",
            "install method": "other",
            "commit": "unknown"
          }
        },
        "sandbox.helpers": {
          "id": "sandbox.helpers",
          "category": "Configuration",
          "status": "ok",
          "summary": "restricted fs + restricted network · approval OnRequest",
          "details": {
            "approval policy": "OnRequest",
            "filesystem sandbox": "restricted",
            "network sandbox": "restricted"
          }
        }
      }
    }
    ```
    
    ### `/feedback` new sentry attachment
    
    <img width="938" height="798" alt="CleanShot 2026-05-13 at 15 36 14"
    src="https://github.com/user-attachments/assets/715e62e0-d7b4-4fea-a35a-fd5d5d33c4c0"
    />
    
    ### New section in CLI issue template
    
    <img width="1164" height="435" alt="CleanShot 2026-05-13 at 15 47 24"
    src="https://github.com/user-attachments/assets/9081dc25-a28c-4afa-8ba1-e299c2b4031d"
    />
    
    ## How to Test
    
    1. Run `cargo run --bin codex -- doctor --no-color`.
    2. Confirm the detailed report is the default and includes promoted
    Notes, grouped sections, terminal details, state DB integrity, rollout
    stats, provider reachability, WebSocket diagnostics, and app-server
    status.
    3. Run `cargo run --bin codex -- doctor --summary --no-color`.
    4. Confirm the compact view keeps the same sections and summary counts
    but omits detailed key/value rows.
    5. Run `cargo run --bin codex -- doctor --json`.
    6. Confirm the output is redacted JSON, `checks` is an object keyed by
    check id, and each check's `details` is a key/value object.
    7. Preview the CLI bug issue template and confirm the `Codex doctor
    report` field appears after the terminal field, asks for `codex doctor
    --json`, and renders pasted output as JSON.
    8. Start a feedback flow that includes logs.
    9. Confirm the upload consent copy lists `codex-doctor-report.json`
    alongside the log attachments.
    
    Targeted tests:
    
    - `cargo test -p codex-cli doctor`
    - `cargo test -p codex-app-server
    doctor_report_tags_summarize_status_counts`
    - `cargo test -p codex-feedback`
    - `cargo test -p codex-tui feedback_view`
    - `just argument-comment-lint`
    - `git diff --check`
  • [codex] Fix TUI wrapping for external borrowed slices (#21235)
    Fixes #20587, reported by @noeljackson.
    
    This prevents the TUI wrapping code from panicking when `textwrap`
    returns a borrowed slice that does not point into the original source
    text. The fix follows the direction proposed by @misrtjakub in the issue
    comment: validate the borrowed slice pointer range first, and fall back
    to the existing owned-line mapper when the slice is external.
    
    - Guards borrowed wrapped slices before converting pointer offsets into
    byte ranges.
    - Reuses the existing owned-line range recovery path for external
    borrowed slices.
    - Adds coverage for rejecting borrowed slices outside the source text.
    
    End-user testing steps:
    - Start Codex in TUI mode under a PTY wrapper that can inject stdin
    after startup.
    - Inject `\x1b[200~test message\x1b[201~\r` after the TUI is ready.
    - Confirm Codex does not panic and the pasted text is handled normally.
    
    Local validation:
    - `cargo test -p codex-tui wrapping::tests::`
    - `cargo test -p codex-tui -- --skip
    status::tests::status_permissions_full_disk_managed_with_network_is_danger_full_access
    --skip
    status::tests::status_permissions_full_disk_managed_without_network_is_external_sandbox`
  • Use plugin/list to get list of plugins for mentions (#22375)
    This switches TUI plugin mentions to use app-server `plugin/list` for
    plugin inventory and metadata instead of `PluginManager`, while keeping
    the same mention-eligibility filters as before.
    
    Same filters as before:
    - Only plugins in the current config / cwd scope.
    - Only installed and enabled plugins.
    - Only plugins that actually expose a capability, meaning at least one
    skill, MCP server, or app connector.
    - Uses `plugin/list` for the mention names/descriptions
  • Enable plugin hooks by default (#22549)
    # Why
    
    Plugin-bundled hooks are already wired through the plugin manager,
    session setup, and app-server hook listing paths. Keeping `plugin_hooks`
    disabled by default means users still need an explicit feature opt-in
    before that existing behavior participates in normal plugin loading.
    
    # What
    
    - mark `plugin_hooks` as stable and enable it by default
    - add feature-registry test coverage for the new default/stage pairing
    
    Validation:
    
    - `cargo test -p codex-features`
    - `just fmt`
  • Add callback ids to local MCP OAuth redirects (#20237)
    ## Summary
    
    - Add a deterministic callback-id path segment to local MCP OAuth
    redirect URIs before starting authorization.
    - Derive the callback id from the normalized MCP server URL and encode
    it as a 12-character URL-safe hash.
    - Reuse the existing exact callback-path validation so OAuth completion
    only succeeds on the callback path that was sent in the redirect URI.
    
    ## Context
    
    Slack thread:
    https://openai.slack.com/archives/C087WB3AGCR/p1777480566571699
    
    That thread calls out the OAuth mix-up class of issue for MCP servers.
    The connector/App Connect flow already has a callback_id concept that
    binds the OAuth callback URL to the MCP app/server identity. Codex
    desktop's local MCP OAuth flow was still using a generic local callback
    path like `/callback`, so this PR adds the same shape to the shared
    local MCP OAuth helper.
    
    ## Behavior
    
    Before this change, local MCP OAuth used:
    
    - default local callback URL: `http://127.0.0.1:<port>/callback`
    - configured callback URL: `<configured callback URL>` unchanged
    
    After this change, Codex appends a deterministic callback-id segment:
    
    - default local callback URL:
    `http://127.0.0.1:<port>/callback/<callback_id>`
    - configured callback URL: `<configured callback path>/<callback_id>`
    
    The local callback server already compares the incoming request path
    against the path from the redirect URI. By appending the callback id
    before both authorization and callback validation, callbacks that arrive
    on the old generic path or a mismatched callback-id path are rejected.
    
    The callback id is bound to the MCP endpoint URL, including path and
    query, so path-based multi-tenant MCP deployments on the same origin do
    not share a callback path. URL fragments are ignored because they are
    not sent to the server.
    
    The change lives in `codex-rmcp-client`, so it covers both the normal
    desktop MCP OAuth login path and silent/plugin-triggered MCP OAuth login
    paths that use the same `perform_oauth_login_*` helpers.
    
    ## Scope and non-goals
    
    - This does not change the app-server protocol or desktop webview
    request shape.
    - This does not implement RFC 9207 `iss` validation; issuer validation
    is still useful when providers return `iss`.
    - This does not make arbitrary untrusted MCP servers safe to use. It
    specifically adds callback URL binding for the local MCP OAuth flow.
    
    ## Validation
    
    - `cargo fmt --all`
    - `cargo test -p codex-rmcp-client perform_oauth_login`
  • Use selected environment cwd for filesystem helpers (#22542)
    ## Why
    
    `TurnContext::cwd` is deprecated in favor of resolving paths from the
    selected turn environment cwd. A few filesystem-oriented paths were
    still constructing sandbox context from the legacy cwd and then mutating
    it afterward, or resolving local file paths through the deprecated
    helper.
    
    ## What changed
    
    - Make `TurnContext::file_system_sandbox_context` take the trusted cwd
    explicitly.
    - Pass the selected turn environment cwd directly from `apply_patch` and
    `view_image` call sites.
    - Restrict `spawn_agents_on_csv` to exactly one local environment and
    resolve input/output CSV paths from that local environment cwd.
    - Remove a redundant test setup assignment that only synchronized
    deprecated `TurnContext::cwd` with a replaced config.
    
    ## Validation
    
    - `cargo test -p codex-core view_image`
    - `cargo test -p codex-core
    maybe_persist_mcp_tool_approval_writes_project_config_for_project_server`
    - `cargo test -p codex-core parse_csv_supports_quotes_and_commas`
    - `git diff --check`
  • Shard Bazel Windows tests across jobs (#22408)
    ## Summary
    - split the single PR-blocking Bazel Windows test leg into four Windows
    shard jobs
    - preserve the existing required Windows Bazel check name with a
    lightweight aggregate gate
    - keep Linux/macOS Bazel test jobs and the separate Windows
    clippy/release jobs unchanged
    
    ## Why
    The ordinary PR Windows Bazel test leg was one GitHub Actions job, so
    Bazel only had in-job parallelism. This gives that lane real job-level
    fanout across separate Windows hosts while keeping the target set
    disjoint via stable label hashing.
    
    ## Evidence
    - final pre-rebase green run: `25774733562`
    - Windows shard target counts: `61/212`, `48/212`, `52/212`, `51/212`
    - Windows test fanout completed in about 7m29s versus a recent
    monolithic median around 22m26s
    
    ## Notes
    - this is scoped to the Bazel Windows test leg only
    - each shard keeps the existing Windows cross-compile/RBE path and
    restores the former monolithic Windows test cache
    - shard jobs do not upload duplicate repository caches after test work,
    keeping cache cleanup off the PR-blocking shard path
    - no local validation run; relying on GitHub Actions for the
    workflow-shaped check
    
    Co-authored-by: Codex <noreply@openai.com>
  • fix: prevent codex-backend from stealing originator (#22533)
    ## Why
    
    Remote control starts by letting `codex-backend` initialize against the
    app-server as an infrastructure health/proxy client before the real
    remote client connects. App-server initialization also sets the
    process-wide `originator` from `client_info.name`, so `codex-backend`
    could become the sticky originator for later model/API requests even
    after the real client initialized.
    
    ## What changed
    
    - Treat `codex-backend` as a non-originating initialize client,
    alongside the existing `codex_app_server_daemon` probe client.
    - Preserve normal per-connection initialize behavior, including session
    metadata and initialize analytics.
    - Add regression coverage that verifies `codex-backend` initialize does
    not replace the default originator.
    
    ## Testing
    
    - `cargo test -p codex-app-server --test all
    initialize_codex_backend_does_not_override_originator`
  • chore(config) rm tools.view_image (#22501)
    ## Summary
    It appears this config flag has been broken/a noop for quite some time:
    since https://github.com/openai/codex/pull/8850. Let's simplify and get
    rid of this.
    
    ## Testing
    - [x] Updated unit tests
  • chore(config) rm Feature::CodexGitCommit (#22412)
    ## Summary
    Removes the unused Feature::CodexGitCommit
    
    ## Testing
    - [x] tests pass