From 8a26ea0fe0e2c348bf3673241732283a7233a02a Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 8 Aug 2025 13:42:33 -0700 Subject: [PATCH 1/2] fix: stop building codex-exec and codex-linux-sandbox binaries (#2036) Release builds are taking awhile and part of the reason that we are building binaries that we are not really using. Adding Windows binaries into releases (https://github.com/openai/codex/pull/2035) slows things down, so we need to get some time back. - `codex-exec` is basically a standalone `codex exec` that we were offering because it's a bit smaller as it does not include all the bits to power the TUI. We were using it in our experimental GitHub Action, so this PR updates the Action to use `codex exec` instead. - `codex-linux-sandbox` was a helper binary for the TypeScript version of the CLI, but I am about to axe that, so we don't need this either. If we decide to bring `codex-exec` back at some point, we should use a separate instances so we can build it in parallel with `codex`. (I think if we had beefier build machines, this wouldn't be so bad, but that's not the case with the default runners from GitHub.) --- .github/actions/codex/action.yml | 12 ++++----- .github/actions/codex/src/run-codex.ts | 4 ++- .github/dotslash-config.json | 36 ++++++++++++-------------- .github/workflows/rust-release.yml | 15 +---------- 4 files changed, 25 insertions(+), 42 deletions(-) diff --git a/.github/actions/codex/action.yml b/.github/actions/codex/action.yml index 404194c00f..011cbccfdf 100644 --- a/.github/actions/codex/action.yml +++ b/.github/actions/codex/action.yml @@ -82,20 +82,18 @@ runs: # Note that if we start baking version numbers into the artifact name, # we will need to update this action.yml file to match. - artifact="codex-exec-${triple}.tar.gz" + artifact="codex-${triple}.tar.gz" TAG_ARG="${{ inputs.codex_release_tag }}" # The usage is `gh release download [] [flags]`, so if TAG_ARG # is empty, we do not pass it so we can default to the latest release. gh release download ${TAG_ARG:+$TAG_ARG} --repo openai/codex \ --pattern "$artifact" --output - \ - | tar xzO > /usr/local/bin/codex-exec - chmod +x /usr/local/bin/codex-exec + | tar xzO > /usr/local/bin/codex + chmod +x /usr/local/bin/codex - # Display Codex version to confirm binary integrity; ensure we point it - # at the checked-out repository via --cd so that any subsequent commands - # use the correct working directory. - codex-exec --cd "$GITHUB_WORKSPACE" --version + # Display Codex version to confirm binary integrity. + codex --version - name: Install Bun uses: oven-sh/setup-bun@v2 diff --git a/.github/actions/codex/src/run-codex.ts b/.github/actions/codex/src/run-codex.ts index 2c851823e8..3c0255e2ec 100644 --- a/.github/actions/codex/src/run-codex.ts +++ b/.github/actions/codex/src/run-codex.ts @@ -18,7 +18,9 @@ export async function runCodex( const tempDirPath = await mkdtemp(join(tmpdir(), "codex-")); const lastMessageOutput = join(tempDirPath, "codex-prompt.md"); - const args = ["/usr/local/bin/codex-exec"]; + // Use the unified CLI and its `exec` subcommand instead of the old + // standalone `codex-exec` binary. + const args = ["/usr/local/bin/codex", "exec"]; const inputCodexArgs = ctx.tryGet("INPUT_CODEX_ARGS")?.trim(); if (inputCodexArgs) { diff --git a/.github/dotslash-config.json b/.github/dotslash-config.json index 1e32001e66..82b9eb93b3 100644 --- a/.github/dotslash-config.json +++ b/.github/dotslash-config.json @@ -1,27 +1,23 @@ { "outputs": { - "codex-exec": { - "platforms": { - "macos-aarch64": { "regex": "^codex-exec-aarch64-apple-darwin\\.zst$", "path": "codex-exec" }, - "macos-x86_64": { "regex": "^codex-exec-x86_64-apple-darwin\\.zst$", "path": "codex-exec" }, - "linux-x86_64": { "regex": "^codex-exec-x86_64-unknown-linux-musl\\.zst$", "path": "codex-exec" }, - "linux-aarch64": { "regex": "^codex-exec-aarch64-unknown-linux-musl\\.zst$", "path": "codex-exec" } - } - }, - "codex": { "platforms": { - "macos-aarch64": { "regex": "^codex-aarch64-apple-darwin\\.zst$", "path": "codex" }, - "macos-x86_64": { "regex": "^codex-x86_64-apple-darwin\\.zst$", "path": "codex" }, - "linux-x86_64": { "regex": "^codex-x86_64-unknown-linux-musl\\.zst$", "path": "codex" }, - "linux-aarch64": { "regex": "^codex-aarch64-unknown-linux-musl\\.zst$", "path": "codex" } - } - }, - - "codex-linux-sandbox": { - "platforms": { - "linux-x86_64": { "regex": "^codex-linux-sandbox-x86_64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" }, - "linux-aarch64": { "regex": "^codex-linux-sandbox-aarch64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" } + "macos-aarch64": { + "regex": "^codex-aarch64-apple-darwin\\.zst$", + "path": "codex" + }, + "macos-x86_64": { + "regex": "^codex-x86_64-apple-darwin\\.zst$", + "path": "codex" + }, + "linux-x86_64": { + "regex": "^codex-x86_64-unknown-linux-musl\\.zst$", + "path": "codex" + }, + "linux-aarch64": { + "regex": "^codex-aarch64-unknown-linux-musl\\.zst$", + "path": "codex" + } } } } diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 32c9669110..ea4a3574fa 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -95,7 +95,7 @@ jobs: sudo apt install -y musl-tools pkg-config - name: Cargo build - run: cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-exec --bin codex-linux-sandbox + run: cargo build --target ${{ matrix.target }} --release --bin codex - name: Stage artifacts shell: bash @@ -104,23 +104,11 @@ jobs: mkdir -p "$dest" if [[ "${{ matrix.runner }}" == windows* ]]; then - cp target/${{ matrix.target }}/release/codex-exec.exe "$dest/codex-exec-${{ matrix.target }}.exe" cp target/${{ matrix.target }}/release/codex.exe "$dest/codex-${{ matrix.target }}.exe" else - cp target/${{ matrix.target }}/release/codex-exec "$dest/codex-exec-${{ matrix.target }}" cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}" fi - # After https://github.com/openai/codex/pull/1228 is merged and a new - # release is cut with an artifacts built after that PR, the `-gnu` - # variants can go away as we will only use the `-musl` variants. - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-musl' }} - name: Stage Linux-only artifacts - shell: bash - run: | - dest="dist/${{ matrix.target }}" - cp target/${{ matrix.target }}/release/codex-linux-sandbox "$dest/codex-linux-sandbox-${{ matrix.target }}" - - name: Compress artifacts shell: bash run: | @@ -133,7 +121,6 @@ jobs: # we publish. The end result is: # codex-.zst (existing) # codex-.tar.gz (new) - # ...same naming for codex-exec-* and codex-linux-sandbox-* # 1. Produce a .tar.gz for every file in the directory *before* we # run `zstd --rm`, because that flag deletes the original files. From 0a83828bb96d9f1033b26f21420a21819b07474e Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 8 Aug 2025 14:16:04 -0700 Subject: [PATCH 2/2] feat: include Windows binary of the CLI in the npm release --- codex-cli/bin/codex.js | 250 ++++++++++------------- codex-cli/scripts/install_native_deps.sh | 47 ++--- codex-cli/scripts/stage_release.sh | 38 +--- codex-cli/scripts/stage_rust_release.py | 1 - 4 files changed, 133 insertions(+), 203 deletions(-) diff --git a/codex-cli/bin/codex.js b/codex-cli/bin/codex.js index df06dd36a7..dc5ad12051 100755 --- a/codex-cli/bin/codex.js +++ b/codex-cli/bin/codex.js @@ -1,23 +1,8 @@ #!/usr/bin/env node // Unified entry point for the Codex CLI. -/* - * Behavior - * ========= - * 1. By default we import the JavaScript implementation located in - * dist/cli.js. - * - * 2. Developers can opt-in to a pre-compiled Rust binary by setting the - * environment variable CODEX_RUST to a truthy value (`1`, `true`, etc.). - * When that variable is present we resolve the correct binary for the - * current platform / architecture and execute it via child_process. - * - * If the CODEX_RUST=1 is specified and there is no native binary for the - * current platform / architecture, an error is thrown. - */ -import fs from "fs"; import path from "path"; -import { fileURLToPath, pathToFileURL } from "url"; +import { fileURLToPath } from "url"; // Determine whether the user explicitly wants the Rust CLI. @@ -25,130 +10,117 @@ import { fileURLToPath, pathToFileURL } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -// For the @native release of the Node module, the `use-native` file is added, -// indicating we should default to the native binary. For other releases, -// setting CODEX_RUST=1 will opt-in to the native binary, if included. -const wantsNative = fs.existsSync(path.join(__dirname, "use-native")) || - (process.env.CODEX_RUST != null - ? ["1", "true", "yes"].includes(process.env.CODEX_RUST.toLowerCase()) - : false); +const { platform, arch } = process; -// Try native binary if requested. -if (wantsNative && process.platform !== 'win32') { - const { platform, arch } = process; - - let targetTriple = null; - switch (platform) { - case "linux": - case "android": - switch (arch) { - case "x64": - targetTriple = "x86_64-unknown-linux-musl"; - break; - case "arm64": - targetTriple = "aarch64-unknown-linux-musl"; - break; - default: - break; - } - break; - case "darwin": - switch (arch) { - case "x64": - targetTriple = "x86_64-apple-darwin"; - break; - case "arm64": - targetTriple = "aarch64-apple-darwin"; - break; - default: - break; - } - break; - default: - break; - } - - if (!targetTriple) { - throw new Error(`Unsupported platform: ${platform} (${arch})`); - } - - const binaryPath = path.join(__dirname, "..", "bin", `codex-${targetTriple}`); - - // Use an asynchronous spawn instead of spawnSync so that Node is able to - // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is - // executing. This allows us to forward those signals to the child process - // and guarantees that when either the child terminates or the parent - // receives a fatal signal, both processes exit in a predictable manner. - const { spawn } = await import("child_process"); - - const child = spawn(binaryPath, process.argv.slice(2), { - stdio: "inherit", - env: { ...process.env, CODEX_MANAGED_BY_NPM: "1" }, - }); - - child.on("error", (err) => { - // Typically triggered when the binary is missing or not executable. - // Re-throwing here will terminate the parent with a non-zero exit code - // while still printing a helpful stack trace. - // eslint-disable-next-line no-console - console.error(err); - process.exit(1); - }); - - // Forward common termination signals to the child so that it shuts down - // gracefully. In the handler we temporarily disable the default behavior of - // exiting immediately; once the child has been signaled we simply wait for - // its exit event which will in turn terminate the parent (see below). - const forwardSignal = (signal) => { - if (child.killed) { - return; +let targetTriple = null; +switch (platform) { + case "linux": + case "android": + switch (arch) { + case "x64": + targetTriple = "x86_64-unknown-linux-musl"; + break; + case "arm64": + targetTriple = "aarch64-unknown-linux-musl"; + break; + default: + break; } - try { - child.kill(signal); - } catch { - /* ignore */ + break; + case "darwin": + switch (arch) { + case "x64": + targetTriple = "x86_64-apple-darwin"; + break; + case "arm64": + targetTriple = "aarch64-apple-darwin"; + break; + default: + break; } - }; - - ["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => { - process.on(sig, () => forwardSignal(sig)); - }); - - // When the child exits, mirror its termination reason in the parent so that - // shell scripts and other tooling observe the correct exit status. - // Wrap the lifetime of the child process in a Promise so that we can await - // its termination in a structured way. The Promise resolves with an object - // describing how the child exited: either via exit code or due to a signal. - const childResult = await new Promise((resolve) => { - child.on("exit", (code, signal) => { - if (signal) { - resolve({ type: "signal", signal }); - } else { - resolve({ type: "code", exitCode: code ?? 1 }); - } - }); - }); - - if (childResult.type === "signal") { - // Re-emit the same signal so that the parent terminates with the expected - // semantics (this also sets the correct exit code of 128 + n). - process.kill(process.pid, childResult.signal); - } else { - process.exit(childResult.exitCode); - } -} else { - // Fallback: execute the original JavaScript CLI. - - // Resolve the path to the compiled CLI bundle - const cliPath = path.resolve(__dirname, "../dist/cli.js"); - const cliUrl = pathToFileURL(cliPath).href; - - // Load and execute the CLI - try { - await import(cliUrl); - } catch (err) { - // eslint-disable-next-line no-console - console.error(err); - process.exit(1); - } + break; + case "win32": + switch (arch) { + case "x64": + targetTriple = "x86_64-pc-windows-msvc"; + break; + case "arm64": + // We do not build this today, fall through... + default: + break; + } + break; + default: + break; } + +if (!targetTriple) { + throw new Error(`Unsupported platform: ${platform} (${arch})`); +} + +// add .exe +const binaryPath = path.join(__dirname, "..", "bin", `codex-${targetTriple}`); + +// Use an asynchronous spawn instead of spawnSync so that Node is able to +// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is +// executing. This allows us to forward those signals to the child process +// and guarantees that when either the child terminates or the parent +// receives a fatal signal, both processes exit in a predictable manner. +const { spawn } = await import("child_process"); + +const child = spawn(binaryPath, process.argv.slice(2), { + stdio: "inherit", + env: { ...process.env, CODEX_MANAGED_BY_NPM: "1" }, +}); + +child.on("error", (err) => { + // Typically triggered when the binary is missing or not executable. + // Re-throwing here will terminate the parent with a non-zero exit code + // while still printing a helpful stack trace. + // eslint-disable-next-line no-console + console.error(err); + process.exit(1); +}); + +// Forward common termination signals to the child so that it shuts down +// gracefully. In the handler we temporarily disable the default behavior of +// exiting immediately; once the child has been signaled we simply wait for +// its exit event which will in turn terminate the parent (see below). +const forwardSignal = (signal) => { + if (child.killed) { + return; + } + try { + child.kill(signal); + } catch { + /* ignore */ + } +}; + +["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => { + process.on(sig, () => forwardSignal(sig)); +}); + +// When the child exits, mirror its termination reason in the parent so that +// shell scripts and other tooling observe the correct exit status. +// Wrap the lifetime of the child process in a Promise so that we can await +// its termination in a structured way. The Promise resolves with an object +// describing how the child exited: either via exit code or due to a signal. +const childResult = await new Promise((resolve) => { + child.on("exit", (code, signal) => { + if (signal) { + resolve({ type: "signal", signal }); + } else { + resolve({ type: "code", exitCode: code ?? 1 }); + } + }); +}); + +if (childResult.type === "signal") { + // Re-emit the same signal so that the parent terminates with the expected + // semantics (this also sets the correct exit code of 128 + n). + process.kill(process.pid, childResult.signal); +} else { + process.exit(childResult.exitCode); +} + diff --git a/codex-cli/scripts/install_native_deps.sh b/codex-cli/scripts/install_native_deps.sh index 353ffafdba..3f3dcd9b26 100755 --- a/codex-cli/scripts/install_native_deps.sh +++ b/codex-cli/scripts/install_native_deps.sh @@ -2,13 +2,8 @@ # Install native runtime dependencies for codex-cli. # -# By default the script copies the sandbox binaries that are required at -# runtime. When called with the --full-native flag, it additionally -# bundles pre-built Rust CLI binaries so that the resulting npm package can run -# the native implementation when users set CODEX_RUST=1. -# # Usage -# install_native_deps.sh [--full-native] [--workflow-url URL] [CODEX_CLI_ROOT] +# install_native_deps.sh [--workflow-url URL] [CODEX_CLI_ROOT] # # The optional RELEASE_ROOT is the path that contains package.json. Omitting # it installs the binaries into the repository's own bin/ folder to support @@ -21,7 +16,6 @@ set -euo pipefail # ------------------ CODEX_CLI_ROOT="" -INCLUDE_RUST=0 # Until we start publishing stable GitHub releases, we have to grab the binaries # from the GitHub Action that created them. Update the URL below to point to the @@ -30,9 +24,6 @@ WORKFLOW_URL="https://github.com/openai/codex/actions/runs/15981617627" while [[ $# -gt 0 ]]; do case "$1" in - --full-native) - INCLUDE_RUST=1 - ;; --workflow-url) shift || { echo "--workflow-url requires an argument"; exit 1; } if [ -n "$1" ]; then @@ -81,26 +72,20 @@ trap 'rm -rf "$ARTIFACTS_DIR"' EXIT # NB: The GitHub CLI `gh` must be installed and authenticated. gh run download --dir "$ARTIFACTS_DIR" --repo openai/codex "$WORKFLOW_ID" -# Decompress the artifacts for Linux sandboxing. -zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-linux-sandbox-x86_64-unknown-linux-musl.zst" \ - -o "$BIN_DIR/codex-linux-sandbox-x64" - -zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-linux-sandbox-aarch64-unknown-linux-musl.zst" \ - -o "$BIN_DIR/codex-linux-sandbox-arm64" - -if [[ "$INCLUDE_RUST" -eq 1 ]]; then - # x64 Linux - zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-x86_64-unknown-linux-musl.zst" \ - -o "$BIN_DIR/codex-x86_64-unknown-linux-musl" - # ARM64 Linux - zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-aarch64-unknown-linux-musl.zst" \ - -o "$BIN_DIR/codex-aarch64-unknown-linux-musl" - # x64 macOS - zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/codex-x86_64-apple-darwin.zst" \ - -o "$BIN_DIR/codex-x86_64-apple-darwin" - # ARM64 macOS - zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/codex-aarch64-apple-darwin.zst" \ - -o "$BIN_DIR/codex-aarch64-apple-darwin" -fi +# x64 Linux +zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-x86_64-unknown-linux-musl.zst" \ + -o "$BIN_DIR/codex-x86_64-unknown-linux-musl" +# ARM64 Linux +zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-musl/codex-aarch64-unknown-linux-musl.zst" \ + -o "$BIN_DIR/codex-aarch64-unknown-linux-musl" +# x64 macOS +zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/codex-x86_64-apple-darwin.zst" \ + -o "$BIN_DIR/codex-x86_64-apple-darwin" +# ARM64 macOS +zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/codex-aarch64-apple-darwin.zst" \ + -o "$BIN_DIR/codex-aarch64-apple-darwin" +# x64 Windows +zstd -d "$ARTIFACTS_DIR/x86_64-pc-windows-msvc/codex-x86_64-pc-windows-msvc.zst" \ + -o "$BIN_DIR/codex-x86_64-pc-windows-msvc.exe" echo "Installed native dependencies into $BIN_DIR" diff --git a/codex-cli/scripts/stage_release.sh b/codex-cli/scripts/stage_release.sh index cd32ade6f9..bc2dee1436 100755 --- a/codex-cli/scripts/stage_release.sh +++ b/codex-cli/scripts/stage_release.sh @@ -7,15 +7,8 @@ # Usage: # # --tmp : Use instead of a freshly created temp directory. -# --native : Bundle the pre-built Rust CLI binaries for Linux alongside -# the JavaScript implementation (a so-called "fat" package). # -h|--help : Print usage. # -# When --native is supplied we copy the linux-sandbox binaries (as before) and -# additionally fetch / unpack the two Rust targets that we currently support: -# - x86_64-unknown-linux-musl -# - aarch64-unknown-linux-musl -# # NOTE: This script is intended to be run from the repository root via # `pnpm --filter codex-cli stage-release ...` or inside codex-cli with the # helper script entry in package.json (`pnpm stage-release ...`). @@ -27,11 +20,10 @@ set -euo pipefail usage() { cat </dev/null echo "Staged version $VERSION for release in $TMPDIR" -if [[ "$INCLUDE_NATIVE" -eq 1 ]]; then - echo "Verify the CLI:" - echo " node ${TMPDIR}/bin/codex.js --version" - echo " node ${TMPDIR}/bin/codex.js --help" -else - echo "Test Node:" - echo " node ${TMPDIR}/bin/codex.js --help" -fi +echo "Verify the CLI:" +echo " node ${TMPDIR}/bin/codex.js --version" +echo " node ${TMPDIR}/bin/codex.js --help" # Print final hint for convenience -if [[ "$INCLUDE_NATIVE" -eq 1 ]]; then - echo "Next: cd \"$TMPDIR\" && npm publish --tag native" -else - echo "Next: cd \"$TMPDIR\" && npm publish" -fi +echo "Next: cd \"$TMPDIR\" && npm publish" diff --git a/codex-cli/scripts/stage_rust_release.py b/codex-cli/scripts/stage_rust_release.py index 6d1326af92..a2f42e224f 100755 --- a/codex-cli/scripts/stage_rust_release.py +++ b/codex-cli/scripts/stage_rust_release.py @@ -50,7 +50,6 @@ Run this after the GitHub Release has been created and use version, "--workflow-url", workflow["url"], - "--native", ] ) stage_release.check_returncode()