From 3ec18a2c0ad0ee440e868de05403e5d48d91ffab Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 5 May 2026 17:15:46 -0700 Subject: [PATCH 1/2] release: publish standalone bwrap artifacts (#21256) **Summary** - Build Linux `bwrap` before the main release binaries. - Export the release `bwrap` SHA-256 as `CODEX_BWRAP_SHA256` so the Codex binary can verify the bundled fallback. - Sign, stage, and upload `bwrap` alongside the primary Linux release artifacts. **Verification** - YAML parse check for `.github/workflows/rust-release.yml` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/21256). * #21257 * __->__ #21256 --- .github/workflows/rust-release.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index faab87f97b..71aab45394 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -255,6 +255,24 @@ jobs: with: target: ${{ matrix.target }} + - if: ${{ contains(matrix.target, 'linux') }} + name: Build bwrap and export digest + shell: bash + run: | + set -euo pipefail + target="${{ matrix.target }}" + cargo build --target "$target" --release --timings --bin bwrap + + bwrap_path="target/${target}/release/bwrap" + if [[ ! -f "$bwrap_path" ]]; then + echo "bwrap binary ${bwrap_path} not found" + exit 1 + fi + + digest="$(sha256sum "$bwrap_path" | awk '{print $1}')" + echo "CODEX_BWRAP_SHA256=${digest}" >> "$GITHUB_ENV" + echo "Built bwrap ${bwrap_path} with sha256:${digest}" + - name: Cargo build shell: bash run: | @@ -278,7 +296,7 @@ jobs: with: target: ${{ matrix.target }} artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release - binaries: ${{ matrix.binaries }} + binaries: ${{ matrix.binaries }} bwrap - if: ${{ runner.os == 'macOS' }} name: MacOS code signing (binaries) @@ -361,6 +379,12 @@ jobs: fi done + if [[ "${{ matrix.target }}" == *linux* && "${{ matrix.bundle }}" == "primary" ]]; then + cp "target/${{ matrix.target }}/release/bwrap" "$dest/bwrap-${{ matrix.target }}" + cp "target/${{ matrix.target }}/release/bwrap.sigstore" \ + "$dest/bwrap-${{ matrix.target }}.sigstore" + fi + if [[ "${{ matrix.build_dmg }}" == "true" ]]; then cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg" fi From f08db3b767474cd59936d7bf6287733f8822619c Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 5 May 2026 17:15:50 -0700 Subject: [PATCH 2/2] npm: bundle standalone bwrap on Linux --- .github/workflows/ci.yml | 2 ++ codex-cli/scripts/build_npm_package.py | 20 ++++++++++++++++++-- codex-cli/scripts/install_native_deps.py | 9 ++++++++- scripts/install/install.sh | 10 +++++++++- scripts/stage_npm_packages.py | 19 +++++++++++++++++-- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb5ed5b5bf..699ab070c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,10 +52,12 @@ jobs: CODEX_VERSION=0.125.0 WORKFLOW_URL="https://github.com/openai/codex/actions/runs/24901475298" OUTPUT_DIR="${RUNNER_TEMP}" + # This reused workflow predates the standalone bwrap artifact. python3 ./scripts/stage_npm_packages.py \ --release-version "$CODEX_VERSION" \ --workflow-url "$WORKFLOW_URL" \ --package codex \ + --allow-missing-native-component bwrap \ --output-dir "$OUTPUT_DIR" PACK_OUTPUT="${OUTPUT_DIR}/codex-npm-${CODEX_VERSION}.tgz" echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT" diff --git a/codex-cli/scripts/build_npm_package.py b/codex-cli/scripts/build_npm_package.py index eda6c26152..c38fed68d9 100755 --- a/codex-cli/scripts/build_npm_package.py +++ b/codex-cli/scripts/build_npm_package.py @@ -69,8 +69,8 @@ PACKAGE_EXPANSIONS: dict[str, list[str]] = { PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = { "codex": [], - "codex-linux-x64": ["codex", "rg"], - "codex-linux-arm64": ["codex", "rg"], + "codex-linux-x64": ["bwrap", "codex", "rg"], + "codex-linux-arm64": ["bwrap", "codex", "rg"], "codex-darwin-x64": ["codex", "rg"], "codex-darwin-arm64": ["codex", "rg"], "codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"], @@ -87,6 +87,7 @@ PACKAGE_TARGET_FILTERS: dict[str, str] = { PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS) COMPONENT_DEST_DIR: dict[str, str] = { + "bwrap": "codex-resources", "codex": "codex", "codex-responses-api-proxy": "codex-responses-api-proxy", "codex-windows-sandbox-setup": "codex", @@ -137,6 +138,16 @@ def parse_args() -> argparse.Namespace: type=Path, help="Directory containing pre-installed native binaries to bundle (vendor root).", ) + parser.add_argument( + "--allow-missing-native-component", + dest="allow_missing_native_components", + action="append", + default=[], + help=( + "Native component that may be absent from --vendor-src. Intended for CI " + "compatibility with older artifact workflows; releases should not use this." + ), + ) return parser.parse_args() @@ -177,6 +188,7 @@ def main() -> int: staging_dir, native_components, target_filter={target_filter} if target_filter else None, + allow_missing_components=set(args.allow_missing_native_components), ) if release_version: @@ -365,12 +377,14 @@ def copy_native_binaries( staging_dir: Path, components: list[str], target_filter: set[str] | None = None, + allow_missing_components: set[str] | None = None, ) -> None: vendor_src = vendor_src.resolve() if not vendor_src.exists(): raise RuntimeError(f"Vendor source directory not found: {vendor_src}") components_set = {component for component in components if component in COMPONENT_DEST_DIR} + allow_missing_components = allow_missing_components or set() if not components_set: return @@ -399,6 +413,8 @@ def copy_native_binaries( src_component_dir = target_dir / dest_dir_name if not src_component_dir.exists(): + if component in allow_missing_components: + continue raise RuntimeError( f"Missing native component '{component}' in vendor source: {src_component_dir}" ) diff --git a/codex-cli/scripts/install_native_deps.py b/codex-cli/scripts/install_native_deps.py index 58fbd370fc..19d0af5bae 100755 --- a/codex-cli/scripts/install_native_deps.py +++ b/codex-cli/scripts/install_native_deps.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Install Codex native binaries (Rust CLI plus ripgrep helpers).""" +"""Install Codex native binaries (Rust CLI, bwrap, and ripgrep helpers).""" import argparse from contextlib import contextmanager @@ -42,8 +42,15 @@ class BinaryComponent: WINDOWS_TARGETS = tuple(target for target in BINARY_TARGETS if "windows" in target) +LINUX_TARGETS = tuple(target for target in BINARY_TARGETS if "linux" in target) BINARY_COMPONENTS = { + "bwrap": BinaryComponent( + artifact_prefix="bwrap", + dest_dir="codex-resources", + binary_basename="bwrap", + targets=LINUX_TARGETS, + ), "codex": BinaryComponent( artifact_prefix="codex", dest_dir="codex", diff --git a/scripts/install/install.sh b/scripts/install/install.sh index 8c225e4d3b..2fc585d7e9 100755 --- a/scripts/install/install.sh +++ b/scripts/install/install.sh @@ -596,6 +596,10 @@ install_release() { cp "$vendor_root/path/rg" "$stage_release/codex-resources/rg" chmod 0755 "$stage_release/codex" chmod 0755 "$stage_release/codex-resources/rg" + if [ -f "$vendor_root/codex-resources/bwrap" ]; then + cp "$vendor_root/codex-resources/bwrap" "$stage_release/codex-resources/bwrap" + chmod 0755 "$stage_release/codex-resources/bwrap" + fi if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then rm -rf "$release_dir" @@ -611,7 +615,11 @@ release_dir_is_complete() { [ -d "$release_dir" ] && [ -x "$release_dir/codex" ] && [ -x "$release_dir/codex-resources/rg" ] && - [ "$(basename "$release_dir")" = "$expected_version-$expected_target" ] + [ "$(basename "$release_dir")" = "$expected_version-$expected_target" ] && + case "$expected_target" in + *linux*) [ -x "$release_dir/codex-resources/bwrap" ] ;; + *) true ;; + esac } update_current_link() { diff --git a/scripts/stage_npm_packages.py b/scripts/stage_npm_packages.py index 5bbee755e9..2a0cd0028d 100755 --- a/scripts/stage_npm_packages.py +++ b/scripts/stage_npm_packages.py @@ -58,6 +58,16 @@ def parse_args() -> argparse.Namespace: action="store_true", help="Retain temporary staging directories instead of deleting them.", ) + parser.add_argument( + "--allow-missing-native-component", + dest="allow_missing_native_components", + action="append", + default=[], + help=( + "Native component that may be absent from reused workflow artifacts. " + "Intended for CI compatibility only; release staging should not use this." + ), + ) return parser.parse_args() @@ -147,6 +157,8 @@ def main() -> int: packages = expand_packages(list(args.packages)) native_components = collect_native_components(packages) + allow_missing_native_components = set(args.allow_missing_native_components) + native_components_to_install = native_components - allow_missing_native_components vendor_temp_root: Path | None = None vendor_src: Path | None = None @@ -155,12 +167,12 @@ def main() -> int: final_messages = [] try: - if native_components: + if native_components_to_install: workflow_url, resolved_head_sha = resolve_workflow_url( args.release_version, args.workflow_url ) vendor_temp_root = Path(tempfile.mkdtemp(prefix="npm-native-", dir=runner_temp)) - install_native_components(workflow_url, native_components, vendor_temp_root) + install_native_components(workflow_url, native_components_to_install, vendor_temp_root) vendor_src = vendor_temp_root / "vendor" if resolved_head_sha: @@ -185,6 +197,9 @@ def main() -> int: if vendor_src is not None: cmd.extend(["--vendor-src", str(vendor_src)]) + for component in sorted(allow_missing_native_components): + cmd.extend(["--allow-missing-native-component", component]) + try: run_command(cmd) finally: