Commit Graph

43 Commits

  • npm: ship platform packages in Codex package layout (#23637)
    ## Summary
    
    The npm platform packages should stop carrying a bespoke native layout
    now that the release workflow builds canonical Codex package archives.
    Keeping npm on the same `bin/`, `codex-resources/`, and `codex-path/`
    structure lets the Rust package-layout detection behave consistently
    across standalone, npm, and future DotSlash installs.
    
    This changes platform npm packages to stage the `codex-package` artifact
    for each target under `vendor/<target>`. The Node launcher now resolves
    `bin/codex` and prepends `codex-path`, while retaining legacy
    `vendor/<target>/codex` and `vendor/<target>/path` fallback support for
    local development and migration. The npm staging helper downloads
    `codex-package` archives instead of rebuilding the CLI payload from
    individual `codex`, `rg`, `bwrap`, and sandbox helper artifacts.
    
    CI still needs to stage npm packages from historical rust-release
    workflow artifacts that predate package archives, so the staging scripts
    expose an explicit `--allow-legacy-codex-package` fallback. That
    fallback synthesizes the canonical package layout from legacy per-binary
    artifacts and is wired only into the CI smoke path; release staging
    remains strict and continues to require real package archives.
    
    For direct local use, `install_native_deps.py` now points its built-in
    default workflow at the same recent artifact run used by CI and
    automatically enables legacy package synthesis only when
    `--workflow-url` is omitted. Explicit workflow URLs remain strict unless
    callers opt in with `--allow-legacy-codex-package`.
    
    ## Test plan
    
    - `python3 -m py_compile codex-cli/scripts/build_npm_package.py
    codex-cli/scripts/install_native_deps.py scripts/stage_npm_packages.py
    scripts/codex_package/cli.py`
    - `node --check codex-cli/bin/codex.js`
    - `ruby -e 'require "yaml";
    YAML.load_file(".github/workflows/rust-release.yml");
    YAML.load_file(".github/workflows/ci.yml"); puts "ok"'`
    - Staged a synthetic `codex-linux-x64` platform package from a canonical
    vendor tree and verified it copied only `bin/`, `codex-path/`,
    `codex-resources/`, and `codex-package.json`.
    - Imported `install_native_deps.py` and extracted a synthetic
    `codex-package-x86_64-unknown-linux-musl.tar.gz` into `vendor/<target>`.
    - Ran legacy-layout conversion smokes for Linux, Windows, and unsigned
    macOS artifact naming.
    - Ran a synthetic `install_native_deps.py` default-workflow smoke that
    verifies legacy package synthesis is automatic only when
    `--workflow-url` is omitted.
    - `NPM_CONFIG_CACHE="$tmp_dir/npm-cache" python3
    ./scripts/stage_npm_packages.py --release-version 0.125.0 --workflow-url
    https://github.com/openai/codex/actions/runs/26131514935 --package codex
    --allow-legacy-codex-package --output-dir "$tmp_dir"`
    - `node codex-cli/bin/codex.js --version`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23637).
    * #23638
    * __->__ #23637
  • install: consume Codex package archives (#23636)
    ## Summary
    
    Standalone installs should exercise the same canonical package archive
    layout that release builds produce, rather than unpacking npm platform
    packages and reconstructing a parallel install tree.
    
    This updates `install.sh` and `install.ps1` to prefer
    `codex-package-<target>.tar.gz` plus `codex-package_SHA256SUMS`
    introduced in https://github.com/openai/codex/pull/23635, authenticate
    the checksum manifest against GitHub release metadata, verify the
    selected package archive against the authenticated manifest, and install
    the package archive directly.
    
    ## Compatibility Notes
    
    Package installs still leave a compatibility command at `current/codex`
    for managed daemon flows, while visible command shims point at
    `bin/codex` inside the package layout.
    
    Recent releases that predate package archives still publish per-platform
    npm artifacts, so both installers keep a legacy platform npm fallback
    for those versions and verify those archives against release metadata
    directly.
    
    Releases old enough to publish only the single root
    `codex-npm-<version>.tgz` archive are intentionally out of scope. The
    installers fail clearly when neither package archives nor per-platform
    npm archives are present.
    
    On Windows, the runtime helper lookups now recognize package-layout
    installs where `codex.exe` runs from `bin/`, so
    `codex-command-runner.exe` and `codex-windows-sandbox-setup.exe` resolve
    from the top-level `codex-resources/` directory. The direct-sibling and
    older sibling-resource fallbacks are preserved.
    
    ## Test plan
    
    - `sh -n scripts/install/install.sh`
    - `bash -n scripts/install/install.sh`
    - `pwsh -NoProfile -Command '$tokens=$null; $errors=$null; $null =
    [System.Management.Automation.Language.Parser]::ParseFile("scripts/install/install.ps1",
    [ref]$tokens, [ref]$errors); if ($errors.Count) { $errors | Format-List
    *; exit 1 }'`
    - `HOME="$home_dir" CODEX_HOME="$tmp_dir/codex-home"
    CODEX_INSTALL_DIR="$bin_dir" PATH="$bin_dir:$PATH" sh
    scripts/install/install.sh --release 0.125.0`
    - Verified the 0.125.0 isolated install leaves the visible command
    pointed at `current/codex` and includes the legacy `codex-resources/rg`
    payload.
    - `cargo test -p codex-windows-sandbox`
    - `just fix -p codex-windows-sandbox`
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23636).
    * #23638
    * #23637
    * __->__ #23636
  • build: package prebuilt Codex entrypoints (#23586)
    ## Why
    
    The package builder should describe the binaries it is actually
    packaging, not require callers to restate release metadata out of band.
    A caller-provided `--version` flag can drift from the workspace version,
    but running the target entrypoint to discover its version breaks
    cross-target packages when the produced binary cannot execute on the
    build host.
    
    This PR keeps package metadata tied to the repository source of truth by
    reading `[workspace.package].version` from `codex-rs/Cargo.toml`. It
    also prepares the package layout for `codex-app-server` packages: the
    same package structure can now represent either the CLI entrypoint or
    the app-server entrypoint while keeping shared sidecars such as `rg`,
    `bwrap`, and Windows sandbox helpers in the existing package
    directories.
    
    ## What changed
    
    - Removes the `--version` CLI flag from
    `scripts/build_codex_package.py`.
    - Adds Cargo.toml version discovery for `codex-package.json.version` via
    `codex-rs/Cargo.toml`.
    - Adds `--entrypoint-bin` so callers can package a prebuilt entrypoint
    instead of rebuilding it with Cargo.
    - Makes `--variant` an explicit choice between `codex` and
    `codex-app-server`, and uses it to select the cargo binary and packaged
    `bin/` entrypoint name.
    - Updates `scripts/codex_package/README.md` to document variants,
    prebuilt entrypoints, and Cargo.toml version detection.
    
    ## Verification
    
    - Compiled `scripts/build_codex_package.py` and
    `scripts/codex_package/*.py` with `PYTHONDONTWRITEBYTECODE=1`.
    - Ran `scripts/build_codex_package.py --help` and verified `--version`
    is gone while `--variant` and `--entrypoint-bin` are present.
    - Verified the package builder reads version `0.0.0` from
    `codex-rs/Cargo.toml`.
    - Built a fake cross-target `codex-app-server` package using a
    non-executable `--entrypoint-bin`; verified metadata records version
    `0.0.0`, variant `codex-app-server`, and `bin/codex-app-server` as the
    entrypoint.
  • build: default Codex package target and output (#23541)
    ## Why
    
    The package builder should be easy to run during local iteration.
    Requiring callers to provide both a target triple and an output
    directory every time makes the common host-package case more awkward
    than necessary.
    
    This PR keeps explicit overrides available, but makes the default
    invocation useful: build for the current host platform and place the
    package in a fresh temporary directory. Because a temp output path is
    otherwise easy to lose, the builder continues to print the final package
    directory path when it completes.
    
    ## What changed
    
    - Makes `--target` optional and maps the host OS/architecture to
    supported Codex package target triples.
    - Uses GNU Linux target triples for Linux host defaults, while keeping
    the musl targets available for release jobs that pass `--target`
    explicitly.
    - Makes `--package-dir` optional and creates a new `codex-package-*`
    temp directory when omitted.
    - Documents the new defaults in `scripts/codex_package/README.md`.
    
    ## Verification
    
    - Compiled `scripts/build_codex_package.py` and
    `scripts/codex_package/*.py` with `PYTHONDONTWRITEBYTECODE=1`.
    - Ran `scripts/build_codex_package.py --help` from outside the repo.
    - Verified Linux host detection maps `x86_64` and `aarch64` to GNU
    target triples.
    - Ran a fake-Cargo package build while omitting both `--target` and
    `--package-dir`; verified the generated metadata target, expected
    package files, and printed temp package path.
    - Ran a fake-Cargo package build for `x86_64-unknown-linux-gnu` and
    verified `codex`, `bwrap`, and `rg` are assembled into the package.
  • build: fetch rg for Codex packages (#23526)
    ## Why
    
    The Codex package builder should produce a complete package without
    requiring callers to pre-populate `rg` under `codex-cli/vendor` or have
    `dotslash` installed on `PATH`. The repo already tracks the
    authoritative DotSlash manifest in `codex-cli/bin/rg`, so the builder
    can read that metadata directly and fetch the correct ripgrep archive
    for the target it is packaging.
    
    ## What changed
    
    - Added `scripts/codex_package/ripgrep.py` to parse `codex-cli/bin/rg`
    after stripping the shebang, select the target platform entry, download
    the configured artifact, and verify the recorded size and SHA-256
    digest.
    - Added a cache under `$TMPDIR/codex-package/<target>-rg` so verified
    archives can be reused without fetching again.
    - Extracted `rg`/`rg.exe` from `tar.gz` and `zip` artifacts into the
    package-builder cache, then copied that into `codex-path` through the
    existing package layout flow.
    - Kept `--rg-bin` as an explicit local override for offline tests and
    unusual local workflows.
    - Documented the default `rg` fetch/cache behavior in
    `scripts/codex_package/README.md`.
    
    ## Verification
    
    - Ran wrapper/module syntax compilation.
    - Ran `scripts/build_codex_package.py --help` from `/private/tmp`.
    - Ran a local manifest fetch test covering shebang-stripped manifest
    parsing, `tar.gz` extraction, `zip` extraction, size/SHA-256
    verification, and cache reuse after deleting the original source
    archives.
    - Ran fake-cargo package/archive builds for macOS, Linux, and Windows
    target layouts with `--rg-bin`, including an assertion that generated
    tar archives contain no duplicate member names.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23526).
    * #23541
    * __->__ #23526
  • build: add Codex package builder (#23513)
    ## Why
    
    Codex CLI packaging is currently split across npm staging, standalone
    installers, and release bundle creation, which makes it hard to define
    and validate a single valid package directory. This adds the first
    standalone package builder so later release paths can converge on the
    same canonical layout.
    
    ## What changed
    
    - Added `scripts/build_codex_package.py` as the stable executable
    wrapper around `scripts/codex_package`.
    - Added modules for CLI parsing, target metadata, grouped cargo builds,
    package layout validation, and archive writing.
    - The builder creates a package directory with `codex-package.json`,
    `bin/`, `codex-resources/`, and `codex-path`, and can serialize it as
    `.tar.gz`, `.tar.zst`, or `.zip`.
    - Source-built artifacts are built by one grouped `cargo build`: `codex`
    for all targets, `bwrap` for Linux, and the Windows sandbox helpers for
    Windows. `rg` remains an input because it is vendored from upstream
    rather than built from this repo.
    - Added `scripts/codex_package/README.md` to document the package
    layout, source-built artifacts, and cargo profile behavior.
    
    ## Verification
    
    - Ran wrapper/module syntax compilation.
    - Ran `scripts/build_codex_package.py --help` from `/private/tmp`.
    - Ran fake-cargo package/archive builds for macOS, Linux, and Windows
    target layouts, including an assertion that generated tar archives
    contain no duplicate member names.
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23513).
    * #23526
    * __->__ #23513
  • Fix rust-ci-full failures due to missing bwrap (#21604)
    Since https://github.com/openai/codex/pull/21255, `rust-ci-full` has
    been failing due to a missing `bwrap`.
    
    ```
    thread 'main' panicked at linux-sandbox/src/launcher.rs:43:13:
    bubblewrap is unavailable: no system bwrap was found on PATH and no bundled codex-resources/bwrap binary was found next to the Codex executable
    ```
    
    Since the happy path is now to use the system binary, let's ensure
    that's installed.
    
    
    https://github.com/openai/codex/pull/21604/commits/8d5182663158ee2d15965f39eed26ffa339ecb7d
    was necessary for the `bwrap` executable to be discoverable when the
    working directory is `/`.
    
    I ran `rust-ci-full` at
    https://github.com/openai/codex/actions/runs/25528074506
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • bazel: run sharded rust integration tests (#21057)
    ## Why
    
    Bazel CI was not actually exercising some sharded Rust integration-test
    targets on macOS. The `rules_rust` sharding wrapper expects a symlink
    runfiles tree, but this repo runs Bazel with `--noenable_runfiles`. In
    that configuration the wrapper could fail to find the generated test
    binary, produce an empty test list, and exit successfully. That made
    targets such as `//codex-rs/core:core-all-test` look green even when
    Cargo CI could still catch failures in the same Rust tests.
    
    The coverage gap appears to have been introduced by
    [#18082](https://github.com/openai/codex/pull/18082), which enabled
    rules_rust native sharding on `//codex-rs/core:core-all-test` and the
    other large Rust test labels. The manifest-runfiles setup itself
    predates that change in
    [#10098](https://github.com/openai/codex/pull/10098), but #18082 is
    where the affected integration tests started running through the
    incompatible rules_rust sharding wrapper.
    [#18913](https://github.com/openai/codex/pull/18913) fixed the same
    class of issue for wrapped unit-test shards, but integration-test shards
    were still going through the rules_rust wrapper until this PR.
    
    We still do not have the V8/code-mode pieces stable under the Bazel CI
    cross-compile setup, so this keeps those tests out of Bazel while
    restoring coverage for the rest of the sharded Rust integration suites.
    Cargo CI remains responsible for V8/code-mode coverage for now.
    
    This change did uncover a real failing core test on `main`:
    `approved_folder_write_request_permissions_unblocks_later_apply_patch`.
    That fix is split into
    [#21060](https://github.com/openai/codex/pull/21060), which enables the
    `apply_patch` tool in the test, teaches the aggregate core test binary
    to dispatch the sandboxed filesystem helper, canonicalizes the macOS
    temp patch target, and isolates the core test harness from managed
    local/enterprise config. Keeping that fix separate lets this PR stay
    focused on restoring Bazel coverage while documenting the first failure
    it exposed.
    
    ## What changed
    
    - Build sharded Rust integration tests as manual `*-bin` binaries and
    run them through the existing manifest-aware `workspace_root_test`
    launcher.
    - Keep Bazel sharding on the launcher target so Rust test cases are
    still distributed by stable test-name hashing.
    - Configure Bazel CI to skip Rust tests whose names contain
    `suite::code_mode::`.
    - Exclude the standalone `codex-rs/code-mode` and `codex-rs/v8-poc`
    unit-test targets from `bazel.yml`.
    
    ## Verification
    
    - `bazel query --output=build //codex-rs/core:core-all-test` now shows
    `workspace_root_test` wrapping `//codex-rs/core:core-all-test-bin`.
    - `bazel test --test_output=all --nocache_test_results
    --test_sharding_strategy=disabled //codex-rs/core:core-all-test
    --test_filter=suite::request_permissions_tool::approved_folder_write_request_permissions_unblocks_later_apply_patch`
    runs the actual Rust test body and passes.
    - `bazel test --test_output=errors --nocache_test_results
    --test_env=CODEX_BAZEL_TEST_SKIP_FILTERS=suite::code_mode::
    //codex-rs/core:core-all-test` runs the sharded target with code-mode
    skipped and passes overall locally, with one flaky attempt retried by
    the existing `flaky = True` setting.
  • ci: cross-compile Windows Bazel clippy (#20701)
    ## Why
    
    #20585 moved the Windows Bazel test job to the cross-compile path, but
    the Windows Bazel clippy and verify-release-build jobs were still using
    the native Windows/MSVC-host fallback. Those two jobs became the slowest
    Windows PR legs, even though both are build-only signal and do not need
    to execute the resulting binaries.
    
    ## What Changed
    
    - Switches the Windows Bazel clippy job from
    `--windows-msvc-host-platform` to `--windows-cross-compile`, so clippy
    build actions use Linux RBE while still targeting
    `x86_64-pc-windows-gnullvm`.
    - Switches the Windows Bazel verify-release-build job to
    `--windows-cross-compile` as well. This job only compiles
    `cfg(not(debug_assertions))` Rust code under `fastbuild`, so it does not
    need a native Windows build host.
    - Keeps the old `--skip_incompatible_explicit_targets` behavior only for
    fork/community PRs without `BUILDBUDDY_API_KEY`, where `run-bazel-ci.sh`
    falls back to the local Windows MSVC-host shape.
    - Adds `--windows-cross-compile` support to
    `.github/scripts/run-bazel-query-ci.sh`, so target-discovery queries
    select the same `ci-windows-cross` config as the subsequent build.
    - Threads that option through `scripts/list-bazel-clippy-targets.sh` so
    the Windows clippy job discovers targets under the same platform shape
    as the subsequent clippy build.
    
    ## Verification
    
    Local checks:
    
    ```shell
    bash -n .github/scripts/run-bazel-query-ci.sh
    bash -n scripts/list-bazel-clippy-targets.sh
    ruby -e 'require "yaml"; YAML.load_file(".github/workflows/bazel.yml"); puts "ok"'
    RUNNER_OS=Linux ./scripts/list-bazel-clippy-targets.sh | grep -c -- '-windows-cross-bin$'
    RUNNER_OS=Windows ./scripts/list-bazel-clippy-targets.sh --windows-cross-compile | grep -c -- '-windows-cross-bin$'
    ```
    
    The Linux target-list check reported `0` Windows-cross internal test
    binaries, while the Windows cross target-list check reported `47`,
    preserving the test-code clippy coverage shape from the existing Windows
    job.
  • ci: cross-compile Windows Bazel tests (#20585)
    ## Status
    
    This is the Bazel PR-CI cross-compilation follow-up to #20485. It is
    intentionally split from the Cargo/cargo-xwin release-build PoC so
    #20485 can stay as the historical release-build exploration. The
    unrelated async-utils test cleanup has been moved to #20686, so this PR
    is focused on the Windows Bazel CI path.
    
    The intended tradeoff is now explicit in `.github/workflows/bazel.yml`:
    pull requests get the fast Windows cross-compiled Bazel test leg, while
    post-merge pushes to `main` run both that fast cross leg and a fully
    native Windows Bazel test leg. The native main-only job keeps full
    V8/code-mode coverage and gets a 40-minute timeout because it is less
    latency-sensitive than PR CI. All other Bazel jobs remain at 30 minutes.
    
    ## Why
    
    Windows Bazel PR CI currently does the expensive part of the build on
    Windows. A native Windows Bazel test job on `main` completed in about
    28m12s, leaving very little headroom under the 30-minute job timeout and
    making Windows the slowest PR signal.
    
    #20485 showed that Windows cross-compilation can be materially faster
    for Cargo release builds, but PR CI needs Bazel because Bazel owns our
    test sharding, flaky-test retries, and integration-test layout. This PR
    applies the same high-level shape we already use for macOS Bazel CI:
    compile with remote Linux execution, then run platform-specific tests on
    the platform runner.
    
    The compromise is deliberately signal-aware: code-mode/V8 changes are
    rare enough that PR CI can accept losing the direct V8/code-mode
    smoke-test signal temporarily, while `main` still runs the native
    Windows job post-merge to catch that class of regression. A follow-up PR
    should investigate making the cross-built Windows gnullvm V8 archive
    pass the direct V8/code-mode tests so this tradeoff can eventually go
    away.
    
    ## What Changed
    
    - Adds a `ci-windows-cross` Bazel config that targets
    `x86_64-pc-windows-gnullvm`, uses Linux RBE for build actions, and keeps
    `TestRunner` actions local on the Windows runner.
    - Adds explicit Windows platform definitions for
    `windows_x86_64_gnullvm`, `windows_x86_64_msvc`, and a bridge toolchain
    that lets gnullvm test targets execute under the Windows MSVC host
    platform.
    - Updates the Windows Bazel PR test leg to opt into the cross-compile
    path via `--windows-cross-compile` and `--remote-download-toplevel`.
    - Adds a `test-windows-native-main` job that runs only for `push` events
    on `refs/heads/main`, uses the native Windows Bazel path, includes
    V8/code-mode smoke tests, and has `timeout-minutes: 40`.
    - Keeps fork/community PRs without `BUILDBUDDY_API_KEY` on the previous
    local Windows MSVC-host fallback, including
    `--host_platform=//:local_windows_msvc` and `--jobs=8`.
    - Preserves the existing integration-test shape on non-gnullvm
    platforms, while generating Windows-cross wrapper targets only for
    `windows_gnullvm`.
    - Resolves `CARGO_BIN_EXE_*` values from runfiles at test runtime,
    avoiding hard-coded Cargo paths and duplicate test runfiles.
    - Extends the V8 Bazel patches enough for the
    `x86_64-pc-windows-gnullvm` target and Linux remote execution path.
    - Makes the Windows sandbox test cwd derive from `INSTA_WORKSPACE_ROOT`
    at runtime when Bazel provides it, because cross-compiled binaries may
    contain Linux compile-time paths.
    - Keeps the direct V8/code-mode unit smoke tests out of the Windows
    cross PR path for now while native Windows CI continues to cover them
    post-merge.
    
    ## Command Shape
    
    The fast Windows PR test leg invokes the normal Bazel CI wrapper like
    this:
    
    ```shell
    ./.github/scripts/run-bazel-ci.sh \
      --print-failed-action-summary \
      --print-failed-test-logs \
      --windows-cross-compile \
      --remote-download-toplevel \
      -- \
      test \
      --test_tag_filters=-argument-comment-lint \
      --test_verbose_timeout_warnings \
      --build_metadata=COMMIT_SHA=${GITHUB_SHA} \
      -- \
      //... \
      -//third_party/v8:all \
      -//codex-rs/code-mode:code-mode-unit-tests \
      -//codex-rs/v8-poc:v8-poc-unit-tests
    ```
    
    With the BuildBuddy secret available on Windows, the wrapper selects
    `--config=ci-windows-cross` and appends the important Windows-cross
    overrides after rc expansion:
    
    ```shell
    --host_platform=//:rbe
    --shell_executable=/bin/bash
    --action_env=PATH=/usr/bin:/bin
    --host_action_env=PATH=/usr/bin:/bin
    --test_env=PATH=${CODEX_BAZEL_WINDOWS_PATH}
    ```
    
    The native post-merge Windows job intentionally omits
    `--windows-cross-compile` and does not exclude the V8/code-mode unit
    targets:
    
    ```shell
    ./.github/scripts/run-bazel-ci.sh \
      --print-failed-action-summary \
      --print-failed-test-logs \
      -- \
      test \
      --test_tag_filters=-argument-comment-lint \
      --test_verbose_timeout_warnings \
      --build_metadata=COMMIT_SHA=${GITHUB_SHA} \
      --build_metadata=TAG_windows_native_main=true \
      -- \
      //... \
      -//third_party/v8:all
    ```
    
    ## Research Notes
    
    The existing macOS Bazel CI config already uses the model we want here:
    build actions run remotely with `--strategy=remote`, but `TestRunner`
    actions execute on the macOS runner. This PR mirrors that pattern for
    Windows with `--strategy=TestRunner=local`.
    
    The important Bazel detail is that `rules_rs` is already targeting
    `x86_64-pc-windows-gnullvm` for Windows Bazel PR tests. This PR changes
    where the build actions execute; it does not switch the Bazel PR test
    target to Cargo, `cargo-nextest`, or the MSVC release target.
    
    Cargo release builds differ from this Bazel path for V8: the normal
    Windows Cargo release target is MSVC, and `rusty_v8` publishes prebuilt
    Windows MSVC `.lib.gz` archives. The Bazel PR path targets
    `windows-gnullvm`; `rusty_v8` does not publish a prebuilt Windows
    GNU/gnullvm archive, so this PR builds that archive in-tree. That
    Linux-RBE-built gnullvm archive currently crashes in direct V8/code-mode
    smoke tests, which is why the workflow keeps native Windows coverage on
    `main`.
    
    The less obvious Bazel detail is test wrapper selection. Bazel chooses
    the Windows test wrapper (`tw.exe`) from the test action execution
    platform, not merely from the Rust target triple. The outer
    `workspace_root_test` therefore declares the default test toolchain and
    uses the bridge toolchain above so the test action executes on Windows
    while its inner Rust binary is built for gnullvm.
    
    The V8 investigation exposed a Windows-client gotcha: even when an
    action execution platform is Linux RBE, Bazel can still derive the
    genrule shell path from the Windows client. That produced remote
    commands trying to run `C:\Program Files\Git\usr\bin\bash.exe` on Linux
    workers. The wrapper now passes `--shell_executable=/bin/bash` with
    `--host_platform=//:rbe` for the Windows cross path.
    
    The same Windows-client/Linux-RBE boundary also affected
    `third_party/v8:binding_cc`: a multiline genrule command can carry CRLF
    line endings into Linux remote bash, which failed as `$'\r'`. That
    genrule now keeps the `sed` command on one physical shell line while
    using an explicit Starlark join so the shell arguments stay readable.
    
    ## Verification
    
    Local checks included:
    
    ```shell
    bash -n .github/scripts/run-bazel-ci.sh
    bash -n workspace_root_test_launcher.sh.tpl
    ruby -e "require %q{yaml}; YAML.load_file(%q{.github/workflows/bazel.yml}); puts %q{ok}"
    RUNNER_OS=Linux ./scripts/list-bazel-clippy-targets.sh
    RUNNER_OS=Windows ./scripts/list-bazel-clippy-targets.sh
    RUNNER_OS=Linux ./tools/argument-comment-lint/list-bazel-targets.sh
    RUNNER_OS=Windows ./tools/argument-comment-lint/list-bazel-targets.sh
    ```
    
    The Linux clippy and argument-comment target lists contain zero
    `*-windows-cross-bin` labels, while the Windows lists still include 47
    Windows-cross internal test binaries.
    
    CI evidence:
    
    - Baseline native Windows Bazel test on `main`: success in about 28m12s,
    https://github.com/openai/codex/actions/runs/25206257208/job/73907325959
    - Green Windows-cross Bazel run on the split PR before adding the
    main-only native leg: Windows test 9m16s, Windows release verify 5m10s,
    Windows clippy 4m43s,
    https://github.com/openai/codex/actions/runs/25231890068
    - The latest SHA adds the explicit PR-vs-main tradeoff in `bazel.yml`;
    CI is rerunning on that focused diff.
    
    ## Follow-Up
    
    A subsequent PR should investigate making a cross-built Windows binary
    work with V8/code-mode enabled. Likely options are either making the
    Linux-RBE-built `windows-gnullvm` V8 archive correct at runtime, or
    evaluating whether a Bazel MSVC target/toolchain can reuse the same
    prebuilt MSVC `rusty_v8` archive shape that Cargo release builds already
    use.
  • ci: reuse Bazel CI startup for target-discovery queries (#19232)
    ## Why
    
    A rerun of the Windows Bazel clippy job after
    [#19161](https://github.com/openai/codex/pull/19161) had exactly the
    cache behavior we wanted in BuildBuddy: zero action-cache misses. Even
    so, the GitHub job still took a little over five minutes.
    
    The problem was that the job was paying for two separate Bazel startup
    paths:
    
    1. a `bazel query` to discover extra lint targets
    2. the real `bazel build --config=clippy ...` invocation
    
    On Windows, that query was bypassing the CI Bazel wrapper, so it did not
    reuse the same `--output_user_root`, CI config, or remote-cache setup as
    the real build. In practice that meant the rerun could still cold-start
    a separate Bazel server before the actual clippy build even began.
    
    ## What
    
    - add `.github/scripts/run-bazel-query-ci.sh` to run CI-side Bazel
    queries with the same startup and cache-related flags as the main Bazel
    command
    - switch `scripts/list-bazel-clippy-targets.sh` to use that helper for
    manual `rust_test` target discovery
    - switch `tools/argument-comment-lint/list-bazel-targets.sh` to use the
    same helper
    - simplify `.github/scripts/run-argument-comment-lint-bazel.sh` so its
    Windows-only query path also goes through the shared helper
    
    This keeps the target-discovery queries aligned with the later
    build/test invocation instead of treating them as a separate cold Bazel
    session.
    
    ## Verification
    
    - `bash -n .github/scripts/run-bazel-query-ci.sh`
    - `bash -n scripts/list-bazel-clippy-targets.sh`
    - `bash -n tools/argument-comment-lint/list-bazel-targets.sh`
    - `bash -n .github/scripts/run-argument-comment-lint-bazel.sh`
    - mocked a Windows invocation of `run-bazel-query-ci.sh` and verified it
    forwards `--output_user_root`, `--config=ci-windows`, the BuildBuddy
    auth header, and the repository cache flags
    
    ## Docs
    
    No documentation updates are needed.
  • Significantly improve standalone installer (#17022)
    ## Summary
    
    This PR significantly improves the standalone installer experience.
    
    The main changes are:
    
    1. We now install the codex binary and other dependencies in a
    subdirectory under CODEX_HOME.
    (`CODEX_HOME/packages/standalone/releases/...`)
    
    2. We replace the `codex.js` launcher that npm/bun rely on with logic in
    the Rust binary that automatically resolves its dependencies (like
    ripgrep)
    
    ## Motivation
    
    A few design constraints pushed this work.
    
    1. Currently, the entrypoint to codex is through `codex.js`, which
    forces a node dependency to kick off our rust app. We want to move away
    from this so that the entrypoint to codex does not rely on node or
    external package managers.
    2. Right now, the native script adds codex and its dependencies directly
    to user PATH. Given that codex is likely to add more binary dependencies
    than ripgrep, we want a solution which does not add arbitrary binaries
    to user PATH -- the only one we want to add is the `codex` command
    itself.
    3. We want upgrades to be atomic. We do not want scenarios where
    interrupting an upgrade command can move codex into undefined state (for
    example, having a new codex binary but an old ripgrep binary). This was
    ~possible with the old script.
    4. Currently, the Rust binary uses heuristics to determine which
    installer created it. These heuristics are flaky and are tied to the
    `codex.js` launcher. We need a more stable/deterministic way to
    determine how the binary was installed for standalone.
    5. We do not want conflicting codex installations on PATH. For example,
    the user installing via npm, then installing via brew, then installing
    via standalone would make it unclear which version of codex is being
    launched and make it tough for us to determine the right upgrade
    command.
    
    ## Design
    
    ### Standalone package layout
    
    Standalone installs now live under `CODEX_HOME/packages/standalone`:
    
    ```text
    $CODEX_HOME/
      packages/
        standalone/
          current -> releases/0.111.0-x86_64-unknown-linux-musl
          releases/
            0.111.0-x86_64-unknown-linux-musl/
              codex
              codex-resources/
                rg
    ```
    
    where `standalone/current` is a symlink to a release directory.
    
    On Windows, the release directory has the same shape, with `.exe` names
    and Windows helpers in `codex-resources`:
    
    ```text
    %CODEX_HOME%\
      packages\
        standalone\
          current -> releases\0.111.0-x86_64-pc-windows-msvc
          releases\
            0.111.0-x86_64-pc-windows-msvc\
              codex.exe
              codex-resources\
                rg.exe
                codex-command-runner.exe
                codex-windows-sandbox-setup.exe
    ```
    
    This gives us:
    - atomic upgrades because we can fully stage a release before switching
    `standalone/current`
    - a stable way for the binary to recognize a standalone install from its
    canonical `current_exe()` path under CODEX_HOME
    - a clean place for binary dependencies like `rg`, Windows sandbox
    helpers, and, in the future, our custom `zsh` etc
    
    ### Command location
    
    On Unix, we add a symlink at `~/.local/bin/codex` which points directly
    to the `$CODEX_HOME/packages/standalone/current/codex` binary. This
    becomes the main entrypoint for the CLI.
    
    On Windows, we store the link at
    `%LOCALAPPDATA%\Programs\OpenAI\Codex\bin`.
    
    ### PATH persistence
    
    This is a tricky part of the PR, as there's no ~super reliable way to
    ensure that we end up on PATH without significant tradeoffs.
    
    Most Unix variants will have `~/.local/bin` on PATH already, which means
    we *should* be fine simply registering the command there in most cases.
    However, there are cases where this is not the case. In these cases, we
    directly edit the profile depending on the shell we're in.
    
    - macOS zsh: `~/.zprofile`
    - macOS bash: `~/.bash_profile`
    - Linux zsh: `~/.zshrc`
    - Linux bash: `~/.bashrc`
    - fallback: `~/.profile`
    
    On Windows, we update the User `Path` environment variable directly and
    we don't need to worry about shell profiles.
    
    ### Standalone runtime detection
    
    This PR adds a new shared crate, `codex-install-context`, which computes
    install ownership once per process and caches it in a `OnceLock`.
    
    That context includes:
    - install manager (`Standalone`, `Npm`, `Bun`, `Brew`, `Other`)
    - the managed standalone release directory, when applicable
    - the managed standalone `codex-resources` directory, when present
    - the resolved `rg_command`
    
    The standalone path is detected by canonicalizing `current_exe()`,
    canonicalizing CODEX_HOME via `find_codex_home()`, and checking whether
    the binary is running from under
    `$CODEX_HOME/packages/standalone/releases`.
    
    We intentionally do not use a release metadata file. The binary path is
    the source of truth.
    
    ### Dependency resolution
    
    For standalone installs, `grep_files` now resolves bundled `rg` from
    `codex-resources` next to the Codex binary.
    
    For npm/bun/brew/other installs, `grep_files` falls back to resolving
    `rg` from PATH.
    
    For Windows standalone installs, Windows sandbox helpers are still found
    as direct siblings when present. If they are not direct siblings, the
    lookup also checks the sibling `codex-resources` directory.
    
    ### TUI update path
    
    The TUI now has `UpdateAction::StandaloneUnix` and
    `UpdateAction::StandaloneWindows`, which rerun the standalone install
    commands.
    
    Unix update command:
    
    ```sh
    sh -c "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
    ```
    
    Windows update command:
    
    ```powershell
    powershell -c "irm https://chatgpt.com/codex/install.ps1|iex"
    ```
    
    The Windows updater runs PowerShell directly. We do this because `cmd
    /C` would parse the `|iex` as a cmd pipeline instead of passing it to
    PowerShell.
    
    ## Additional installer behavior
    
    - standalone installs now warn about conflicting npm/bun/brew-managed
    `codex` installs and offer to uninstall them
    - same-version reruns do not redownload the release if it is already
    staged locally
    
    ## Testing
    
    Installer smoke tests run:
    - macOS: fresh install into isolated `HOME` and `CODEX_HOME` with
    `scripts/install/install.sh --release latest`
    - macOS: reran the installer against the same isolated install to verify
    the same-version/update path and PATH block idempotence
    - macOS: verified the installed `codex --version` and bundled
    `codex-resources/rg --version`
    - Windows: parsed `scripts/install/install.ps1` with PowerShell via
    `[scriptblock]::Create(...)`
    - Windows: verified the standalone update action builds a direct
    PowerShell command and does not route the `irm ...|iex` command through
    `cmd /C`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Reuse remote exec-server in core tests (#17837)
    ## Summary
    - reuse a shared remote exec-server for remote-aware codex-core
    integration tests within a test binary process
    - keep per-test remote cwd creation and cleanup so tests retain
    workspace isolation
    - leave codex_self_exe, codex_linux_sandbox_exe, cwd_path(), and
    workspace_path() behavior unchanged
    
    ## Validation
    - rustfmt codex-rs/core/tests/common/test_codex.rs
    - git diff --check
    - CI is running on the updated branch
  • Add Bazel verify-release-build job (#17705)
    ## Why
    
    `main` recently needed
    [#17691](https://github.com/openai/codex/pull/17691) because code behind
    `cfg(not(debug_assertions))` was not being compiled by the Bazel PR
    workflow. Our existing CI only built the fast/debug configuration, so
    PRs could stay green while release-only Rust code still failed to
    compile. This PR adds a release-style compile check that is cheap enough
    to run on every PR.
    
    ## What Changed
    
    - Added a `verify-release-build` job to `.github/workflows/bazel.yml`.
    - Represented each supported OS once in that job's matrix: x64 Linux,
    arm64 macOS, and x64 Windows.
    - Kept the build close to fastbuild cost by using
    `--compilation_mode=fastbuild` while forcing Rust to compile with
    `-Cdebug-assertions=no`, which makes `cfg(not(debug_assertions))` true
    without also turning on release optimizations or debug-info generation.
    - Added comments in `.github/workflows/bazel.yml` and
    `scripts/list-bazel-release-targets.sh` to make the job's intent and
    target scope explicit.
    - Restored the Bazel repository cache save behavior to run after every
    non-cancelled job, matching
    [#16926](https://github.com/openai/codex/pull/16926), and removed the
    now-unused `repository-cache-hit` output from `prepare-bazel-ci`.
    - Reused the shared `prepare-bazel-ci` action from the parent PR so the
    new job does not duplicate Bazel setup boilerplate.
    
    ## Verification
    
    - Used `bazel aquery` on `//codex-rs/tui:codex-tui` to confirm the Rust
    compile still uses `opt-level=0` and `debuginfo=0` while passing
    `-Cdebug-assertions=no`.
    - Parsed `.github/workflows/bazel.yml` as YAML locally.
    - Ran `bash -n scripts/list-bazel-release-targets.sh`.
  • Stabilize exec-server filesystem tests in CI (#17671)
    ## Summary\n- add an exec-server package-local test helper binary that
    can run exec-server and fs-helper flows\n- route exec-server filesystem
    tests through that helper instead of cross-crate codex helper
    binaries\n- stop relying on Bazel-only extra binary wiring for these
    tests\n\n## Testing\n- not run (per repo guidance for codex changes)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Run exec-server fs operations through sandbox helper (#17294)
    ## Summary
    - run exec-server filesystem RPCs requiring sandboxing through a
    `codex-fs` arg0 helper over stdin/stdout
    - keep direct local filesystem execution for `DangerFullAccess` and
    external sandbox policies
    - remove the standalone exec-server binary path in favor of top-level
    arg0 dispatch/runtime paths
    - add sandbox escape regression coverage for local and remote filesystem
    paths
    
    ## Validation
    - `just fmt`
    - `git diff --check`
    - remote devbox: `cd codex-rs && bazel test --bes_backend=
    --bes_results_url= //codex-rs/exec-server:all` (6/6 passed)
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Support remote exec cwd in TUI startup (#17142)
    When running with remote executor the cwd is the remote path. Today we
    check for existence of a local directory on startup and attempt to load
    config from it.
    
    For remote executors don't do that.
  • Add remote exec start script (#17059)
    Just pass an SSH host
    ```
    ./scripts/start-codex-exec.sh codex-remote
    ```
  • [codex] Make unified exec tests remote aware (#16977)
    ## Summary
    - Convert unified exec integration tests that can run against the remote
    executor to use the remote-aware test harness.
    - Create workspace directories through the executor filesystem for
    remote runs.
    - Install `python3` and `zsh` in the remote test container so restored
    Python/zsh-based test commands work in fresh Ubuntu containers.
    
    ## Validation
    - `just fmt`
    - `cargo test -p codex-core --test all unified_exec_defaults_to_pipe`
    - `cargo test -p codex-core --test all unified_exec_can_enable_tty`
    - `cargo test -p codex-core --test all unified_exec`
    - Remote on `codex-remote`: `source scripts/test-remote-env.sh && cd
    codex-rs && cargo test -p codex-core --test all unified_exec`
    - `just fix -p codex-core`
  • ci: align Bazel repo cache and Windows clippy target handling (#16740)
    ## Why
    
    Bazel CI had two independent Windows issues:
    
    - The workflow saved/restored `~/.cache/bazel-repo-cache`, but
    `.bazelrc` configured `common:ci-windows
    --repository_cache=D:/a/.cache/bazel-repo-cache`, so `actions/cache` and
    Bazel could point at different directories.
    - The Windows `Bazel clippy` job passed the full explicit target list
    from `//codex-rs/...`, but some of those explicit targets are
    intentionally incompatible with `//:local_windows`.
    `run-argument-comment-lint-bazel.sh` already handles that with
    `--skip_incompatible_explicit_targets`; the clippy workflow path did
    not.
    
    I also tried switching the workflow cache path to
    `D:\a\.cache\bazel-repo-cache`, but the Windows clippy job repeatedly
    failed with `Failed to restore: Cache service responded with 400`, so
    the final change standardizes on `$HOME/.cache/bazel-repo-cache` and
    makes cache restore non-fatal.
    
    ## What Changed
    
    - Expose one repository-cache path from
    `.github/actions/setup-bazel-ci/action.yml` and export that path as
    `BAZEL_REPOSITORY_CACHE` so `run-bazel-ci.sh` passes it to Bazel after
    `--config=ci-*`.
    - Move `actions/cache/restore` out of the composite action into
    `.github/workflows/bazel.yml`, and make restore failures non-fatal
    there.
    - Save exactly the exported cache path in `.github/workflows/bazel.yml`.
    - Remove `common:ci-windows
    --repository_cache=D:/a/.cache/bazel-repo-cache` from `.bazelrc` so the
    Windows CI config no longer disagrees with the workflow cache path.
    - Pass `--skip_incompatible_explicit_targets` in the Windows `Bazel
    clippy` job so incompatible explicit targets do not fail analysis while
    the lint aspect still traverses compatible Rust dependencies.
    
    ## Verification
    
    - Parsed `.github/actions/setup-bazel-ci/action.yml` and
    `.github/workflows/bazel.yml` with Ruby's YAML loader.
    - Resubmitted PR `#16740`; CI is rerunning on the amended commit.
  • bazel: lint rust_test targets in clippy workflow (#16450)
    ## Why
    
    `cargo clippy --tests` was catching warnings in inline `#[cfg(test)]`
    code that the Bazel PR Clippy lane missed. The existing Bazel invocation
    linted `//codex-rs/...`, but that did not apply Clippy to the generated
    manual `rust_test` binaries, so warnings in targets such as
    `//codex-rs/state:state-unit-tests-bin` only surfaced as plain compile
    warnings instead of failing the lint job.
    
    ## What Changed
    
    - added `scripts/list-bazel-clippy-targets.sh` to expand the Bazel
    Clippy target set with the generated manual `rust_test` rules while
    still excluding `//codex-rs/v8-poc:all`
    - updated `.github/workflows/bazel.yml` to use that expanded target list
    in the Bazel Clippy PR job
    - updated `just bazel-clippy` to use the same target expansion locally
    - updated `.github/workflows/README.md` to document that the Bazel PR
    lint lane now covers inline `#[cfg(test)]` code
    
    ## Verification
    
    - `./scripts/list-bazel-clippy-targets.sh` includes
    `//codex-rs/state:state-unit-tests-bin`
    - `bazel build --config=clippy -- //codex-rs/state:state-unit-tests-bin`
    now fails with the same unused import in `state/src/runtime/logs.rs`
    that `cargo clippy --tests` reports
  • Remove the legacy TUI split (#15922)
    This is the part 1 of 2 PRs that will delete the `tui` /
    `tui_app_server` split. This part simply deletes the existing `tui`
    directory and marks the `tui_app_server` feature flag as removed. I left
    the `tui_app_server` feature flag in place for now so its presence
    doesn't result in an error. It is simply ignored.
    
    Part 2 will rename the `tui_app_server` directory `tui`. I did this as
    two parts to reduce visible code churn.
  • Add cached environment manager for exec server URL (#15785)
    Add environment manager that is a singleton and is created early in
    app-server (before skill manager, before config loading).
    
    Use an environment variable to point to a running exec server.
  • Add remote env CI matrix and integration test (#14869)
    `CODEX_TEST_REMOTE_ENV` will make `test_codex` start the executor
    "remotely" (inside a docker container) turning any integration test into
    remote test.
  • check for large binaries in CI (#14382)
    Prevent binaries >500KB from being committed. And maintain an allowlist
    if we need to bypass on a case-by-case basis.
    
    I checked the currently tracked binary-like assets in the repo. There
    are only 5 obvious committed binaries by extension/MIME type:
    - `.github/codex-cli-splash.png`: `838,131` bytes, about `818 KiB`
    - `codex-rs/vendor/bubblewrap/bubblewrap.jpg`: `40,239` bytes, about `39
    KiB`
    -
    `codex-rs/skills/src/assets/samples/skill-creator/assets/skill-creator.png`:
    `1,563` bytes
    - `codex-rs/skills/src/assets/samples/openai-docs/assets/openai.png`:
    `1,429` bytes
    -
    `codex-rs/skills/src/assets/samples/skill-installer/assets/skill-installer.png`:
    `1,086` bytes
    
    So `500 KB` looks like a good default for this repo. It would only trip
    on one existing intentional asset, which keeps the allowlist small and
    the policy easy to understand.
    
    Here's a smoke-test from a throwaway branch that tries to commit a large
    binary:
    https://github.com/openai/codex/actions/runs/22971558828/job/66689330435?pr=14383
  • Add Windows direct install script (#12741)
    ## Summary
    - add a direct install script for Windows at
    `scripts/install/install.ps1`
    - extend release staging so `install.ps1` is published alongside
    `install.sh`
    - install the Windows runtime payload (`codex.exe`, `rg.exe`, and helper
    binaries) from the existing platform npm package
    
    ## Dependencies
    - Depends on https://github.com/openai/codex/pull/12740
    
    ## Testing
    - Smoke-tested with powershell
  • Add macOS and Linux direct install script (#12740)
    ## Summary
    - add a direct install script for macOS and Linux at
    `scripts/install/install.sh`
    - stage `install.sh` into `dist/` during release so it is published as a
    GitHub release asset
    - reuse the existing platform npm payload so the installer includes both
    `codex` and `rg`
    
    ## Testing
    - `bash -n scripts/install/install.sh`
    - local macOS `curl | sh` smoke test against a locally served copy of
    the script
  • bazel: enforce MODULE.bazel.lock sync with Cargo.lock (#11790)
    ## Why this change
    
    When Cargo dependencies change, it is easy to end up with an unexpected
    local diff in
    `MODULE.bazel.lock` after running Bazel. That creates noisy working
    copies and pushes lockfile fixes
    later in the cycle. This change addresses that pain point directly.
    
    ## What this change enforces
    
    The expected invariant is: after dependency updates, `MODULE.bazel.lock`
    is already in sync with
    Cargo resolution. In practice, running `bazel mod deps` should not
    mutate the lockfile in a clean
    state. If it does, the dependency update is incomplete.
    
    ## How this is enforced
    
    This change adds a single lockfile check script that snapshots
    `MODULE.bazel.lock`, runs
    `bazel mod deps`, and fails if the file changes. The same check is wired
    into local workflow
    commands (`just bazel-lock-update` and `just bazel-lock-check`) and into
    Bazel CI (Linux x86_64 job)
    so drift is caught early and consistently. The developer documentation
    is updated in
    `codex-rs/docs/bazel.md` and `AGENTS.md` to make the expected flow
    explicit.
    
    `MODULE.bazel.lock` is also refreshed in this PR to match the current
    Cargo dependency resolution.
    
    ## Expected developer workflow
    
    After changing `Cargo.toml` or `Cargo.lock`, run `just
    bazel-lock-update`, then run
    `just bazel-lock-check`, and include any resulting `MODULE.bazel.lock`
    update in the same change.
    
    ## Testing
    
    Ran `just bazel-lock-check` locally.
  • # Use @openai/codex dist-tags for platform binaries instead of separate package names (#11339)
    https://github.com/openai/codex/pull/11318 introduced logic to publish
    platform artifacts as separate npm packages (for example,
    `@openai/codex-darwin-arm64`, `@openai/codex-linux-x64`, etc.). That
    requires provisioning and maintaining multiple package entries in npm,
    which we want to avoid.
    
    We still need to keep the package-size mitigation (platform-specific
    payloads), but we want that layout to live under a single npm package
    namespace (`@openai/codex`) using dist-tags.
    
    We also need to preserve pre-release workflows where users install
    `@openai/codex@alpha` and get platform-appropriate binaries.
    
    Additionally, we want GitHub Release assets to group Codex npm tarballs
    together, so platform tarballs should follow the same `codex-npm-*`
    filename prefix as the main Codex tarball.
    
    ## Release Strategy (New Scheme)
    
    We publish **one npm package name for Codex binaries** (`@openai/codex`)
    and use **dist-tags** to select platform-specific payloads. This avoids
    creating separate platform package names while keeping the package size
    split by platform.
    
    ### What gets published
    
    #### Mainline release (`x.y.z`)
    
    - `@openai/codex@latest` (meta package)
    - `@openai/codex@darwin-arm64`
    - `@openai/codex@darwin-x64`
    - `@openai/codex@linux-arm64`
    - `@openai/codex@linux-x64`
    - `@openai/codex@win32-arm64`
    - `@openai/codex@win32-x64`
    - `@openai/codex-responses-api-proxy@latest`
    - `@openai/codex-sdk@latest`
    
    #### Alpha release (`x.y.z-alpha.N`)
    
    - `@openai/codex@alpha` (meta package)
    - `@openai/codex@alpha-darwin-arm64`
    - `@openai/codex@alpha-darwin-x64`
    - `@openai/codex@alpha-linux-arm64`
    - `@openai/codex@alpha-linux-x64`
    - `@openai/codex@alpha-win32-arm64`
    - `@openai/codex@alpha-win32-x64`
    - `@openai/codex-responses-api-proxy@alpha`
    - `@openai/codex-sdk@alpha`
    
    As an example, the `package.json` for `@openai/codex@alpha` (using
    `0.99.0-alpha.17` as the `version`) would be:
    
    ```
    {
      "name": "@openai/codex",
      "version": "0.99.0-alpha.17",
      "license": "Apache-2.0",
      "bin": {
        "codex": "bin/codex.js"
      },
      "type": "module",
      "engines": {
        "node": ">=16"
      },
      "files": [
        "bin"
      ],
      "repository": {
        "type": "git",
        "url": "git+https://github.com/openai/codex.git",
        "directory": "codex-cli"
      },
      "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
      "optionalDependencies": {
        "@openai/codex-linux-x64": "npm:@openai/codex@0.99.0-alpha.17-linux-x64",
        "@openai/codex-linux-arm64": "npm:@openai/codex@0.99.0-alpha.17-linux-arm64",
        "@openai/codex-darwin-x64": "npm:@openai/codex@0.99.0-alpha.17-darwin-x64",
        "@openai/codex-darwin-arm64": "npm:@openai/codex@0.99.0-alpha.17-darwin-arm64",
        "@openai/codex-win32-x64": "npm:@openai/codex@0.99.0-alpha.17-win32-x64",
        "@openai/codex-win32-arm64": "npm:@openai/codex@0.99.0-alpha.17-win32-arm64"
      }
    }
    ```
    
    Note that the keys in `optionalDependencies` have "clean" names, but the
    values have the tag embedded.
    
    ### Important note
    
    **Note:** Because we never created the new platform package names on npm
    (for example,
    `@openai/codex-darwin-arm64`) since #11318 landed, there are no extra
    npm packages to clean up.
    
    ## What changed
    
    ### 1. Stage platform tarballs as `@openai/codex` with platform-specific
    versions
    
    File: `codex-cli/scripts/build_npm_package.py`
    
    - Added `CODEX_NPM_NAME = "@openai/codex"` and platform metadata
    `npm_tag` values:
    - `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`,
    `win32-arm64`, `win32-x64`
    - For platform package staging (`codex-<platform>` inputs), switched
    generated `package.json` from:
      - `name = @openai/codex-<platform>`
      to:
      - `name = @openai/codex`
    - Added `compute_platform_package_version(version, platform_tag)` so
    platform tarballs have unique
    versions (`<release-version>-<platform-tag>`), which is required because
    npm forbids re-publishing
      the same `name@version`.
    
    ### 2. Point meta package optional dependencies at dist-tags on
    `@openai/codex`
    
    File: `codex-cli/scripts/build_npm_package.py`
    
    - Updated `optionalDependencies` generation for the main `codex` package
    to use npm alias syntax:
    - key remains alias package name (for example,
    `@openai/codex-darwin-arm64`) so runtime lookup behavior is unchanged
      - value now resolves to `@openai/codex` by dist-tag
    - Stable releases emit tags like `npm:@openai/codex@darwin-arm64`.
    - Alpha releases (`x.y.z-alpha.N`) emit tags like
    `npm:@openai/codex@alpha-darwin-arm64`.
    
    ### 3. Publish with per-tarball dist-tags in release CI
    
    File: `.github/workflows/rust-release.yml`
    
    - Reworked npm publish logic to derive the publish tag per tarball
    filename:
      - platform tarballs publish with `<platform>` tags for stable releases
    - platform tarballs publish with `alpha-<platform>` tags for alpha
    releases
    - top-level tarballs (`codex`, `codex-responses-api-proxy`, `codex-sdk`)
    continue using
    the existing channel tag policy (`latest` implicit for stable, `alpha`
    for alpha)
    - Added fail-fast behavior for unexpected tarball names to avoid silent
    mispublishes.
    
    ### 4. Normalize Codex platform tarball filenames for GitHub Release
    grouping
    
    Files: `scripts/stage_npm_packages.py`,
    `.github/workflows/rust-release.yml`
    
    - Renamed staged platform tarball filenames from:
      - `codex-linux-<arch>-npm-<version>.tgz`
      - `codex-darwin-<arch>-npm-<version>.tgz`
      - `codex-win32-<arch>-npm-<version>.tgz`
    - To:
      - `codex-npm-linux-<arch>-<version>.tgz`
      - `codex-npm-darwin-<arch>-<version>.tgz`
      - `codex-npm-win32-<arch>-<version>.tgz`
    
    This keeps all Codex npm artifacts grouped under a common `codex-npm-`
    prefix in GitHub Releases.
    
    ### 5. Documentation update
    
    File: `codex-cli/scripts/README.md`
    
    - Updated staging docs to clarify that platform-native variants are
    published as dist-tagged
      `@openai/codex` artifacts rather than separate npm package names.
    
    ## Resulting behavior
    
    - Mainline release:
      - `@openai/codex@latest` resolves the meta package
    - meta package optional dependencies resolve
    `@openai/codex@<platform-tag>`
    - Alpha release:
      - users can continue installing `@openai/codex@alpha`
    - alpha meta package optional dependencies resolve
    `@openai/codex@alpha-<platform-tag>`
    - Release assets:
    - Codex npm tarballs share `codex-npm-` prefix for cleaner grouping in
    GitHub Releases
    
    This preserves platform-specific payload distribution while avoiding
    separate npm package names and
    improves release-asset discoverability.
    
    ## Validation notes
    
    - Verified staged `package.json` output for stable and alpha meta
    packages includes expected alias targets.
    - Verified staged platform package manifests are `name=@openai/codex`
    with unique platform-suffixed versions.
    - Verified publish tag derivation maps renamed platform tarballs to
    expected stable and alpha dist-tags.
  • WebSocket test server script (#9175)
    Very simple test server to test Responses API WebSocket transport
    integration.
  • fix: ToC so it doesn’t include itself or duplicate the end marker (#4388)
    turns out the ToC was including itself when generating, which messed up
    comparisons and sometimes made the file rewrite endlessly.
    
    also fixed the slice so `<!-- End ToC -->` doesn’t get duplicated when
    we insert the new ToC.
    
    should behave nicely now - no extra rewrites, no doubled markers.
    
    Co-authored-by: Eric Traut <etraut@openai.com>
  • Enable plan tool by default (#5384)
    ## Summary
    - make the plan tool available by default by removing the feature flag
    and always registering the handler
    - drop plan-tool CLI and API toggles across the exec, TUI, MCP server,
    and app server code paths
    - update tests and configs to reflect the always-on plan tool and guard
    workspace restriction tests against env leakage
    
    ## Testing
    Manually tested the extension. 
    ------
    https://chatgpt.com/codex/tasks/task_i_68f67a3ff2d083209562a773f814c1f9
  • chore: introduce publishing logic for @openai/codex-sdk (#4543)
    There was a bit of copypasta I put up with when were publishing two
    packages to npm, but now that it's three, I created some more scripts to
    consolidate things.
    
    With this change, I ran:
    
    ```shell
    ./scripts/stage_npm_packages.py --release-version 0.43.0-alpha.8 --package codex --package codex-responses-api-proxy --package codex-sdk
    ```
    
    Indeed when it finished, I ended up with:
    
    ```shell
    $ tree dist
    dist
    └── npm
        ├── codex-npm-0.43.0-alpha.8.tgz
        ├── codex-responses-api-proxy-npm-0.43.0-alpha.8.tgz
        └── codex-sdk-npm-0.43.0-alpha.8.tgz
    $ tar tzvf dist/npm/codex-sdk-npm-0.43.0-alpha.8.tgz
    -rwxr-xr-x  0 0      0    25476720 Oct 26  1985 package/vendor/aarch64-apple-darwin/codex/codex
    -rwxr-xr-x  0 0      0    29871400 Oct 26  1985 package/vendor/aarch64-unknown-linux-musl/codex/codex
    -rwxr-xr-x  0 0      0    28368096 Oct 26  1985 package/vendor/x86_64-apple-darwin/codex/codex
    -rwxr-xr-x  0 0      0    36029472 Oct 26  1985 package/vendor/x86_64-unknown-linux-musl/codex/codex
    -rw-r--r--  0 0      0       10926 Oct 26  1985 package/LICENSE
    -rw-r--r--  0 0      0    30187520 Oct 26  1985 package/vendor/aarch64-pc-windows-msvc/codex/codex.exe
    -rw-r--r--  0 0      0    35277824 Oct 26  1985 package/vendor/x86_64-pc-windows-msvc/codex/codex.exe
    -rw-r--r--  0 0      0        4842 Oct 26  1985 package/dist/index.js
    -rw-r--r--  0 0      0        1347 Oct 26  1985 package/package.json
    -rw-r--r--  0 0      0        9867 Oct 26  1985 package/dist/index.js.map
    -rw-r--r--  0 0      0          12 Oct 26  1985 package/README.md
    -rw-r--r--  0 0      0        4287 Oct 26  1985 package/dist/index.d.ts
    ```
  • README / docs refactor (#2724)
    This PR cleans up the monolithic README by breaking it into a set
    navigable pages under docs/ (install, getting started, configuration,
    authentication, sandboxing and approvals, platform details, FAQ, ZDR,
    contributing, license). The top‑level README is now more concise and
    intuitive, (with corrected screenshots).
    
    It also consolidates overlapping content from codex-rs/README.md into
    the top‑level docs and updates links accordingly. The codex-rs README
    remains in place for now as a pointer and for continuity.
    
    Finally, added an extensive config reference table at the bottom of
    docs/config.md.
    
    ---------
    
    Co-authored-by: easong-openai <easong@openai.com>
  • fix: improve npm release process (#2055)
    This improves the release process by introducing
    `scripts/publish_to_npm.py` to automate publishing to npm (modulo the
    human 2fac step).
    
    As part of this, it updates `.github/workflows/rust-release.yml` to
    create the artifact for npm using `npm pack`.
    
    And finally, while it is long overdue, this memorializes the release
    process in `docs/release_management.md`.
  • add check to ensure ToC in README.md matches headings in the file (#541)
    This introduces a Python script (written by Codex!) to verify that the
    table of contents in the root `README.md` matches the headings. Like
    `scripts/asciicheck.py` in https://github.com/openai/codex/pull/513, it
    reports differences by default (and exits non-zero if there are any) and
    also has a `--fix` option to synchronize the ToC with the headings.
    
    This will be enforced by CI and the changes to `README.md` in this PR
    were generated by the script, so you can see that our ToC was missing
    some entries prior to this PR.
  • Enforce ASCII in README.md (#513)
    This all started because I was going to write a script to autogenerate
    the Table of Contents in the root `README.md`, but I noticed that the
    `href` for the "Why Codex?" heading was `#whycodex` instead of
    `#why-codex`. This piqued my curiosity and it turned out that the space
    in "Why Codex?" was not an ASCII space but **U+00A0**, a non-breaking
    space, and so GitHub ignored it when generating the `href` for the
    heading.
    
    This also meant that when I did a text search for `why codex` in the
    `README.md` in VS Code, the "Why Codex" heading did not match because of
    the presence of **U+00A0**.
    
    In short, these types of Unicode characters seem like a hazard, so I
    decided to introduce this script to flag them, and if desired, to
    replace them with "good enough" ASCII equivalents. For now, this only
    applies to the root `README.md` file, but I think we should ultimately
    apply this across our source code, as well, as we seem to have quite a
    lot of non-ASCII Unicode and it's probably going to cause `rg` to miss
    things.
    
    Contributions of this PR:
    
    * `./scripts/asciicheck.py`, which takes a list of filepaths and returns
    non-zero if any of them contain non-ASCII characters. (Currently, there
    is one exception for  aka **U+2728**, though I would like to default to
    an empty allowlist and then require all exceptions to be specified as
    flags.)
    * A `--fix` option that will attempt to rewrite files with violations
    using a equivalents from a hardcoded substitution list.
    * An update to `ci.yml` to verify `./scripts/asciicheck.py README.md`
    succeeds.
    * A cleanup of `README.md` using the `--fix` option as well as some
    editorial decisions on my part.
    * I tried to update the `href`s in the Table of Contents to reflect the
    changes in the heading titles. (TIL that if a heading has a character
    like `&` surrounded by spaces, it becomes `--` in the generated `href`.)