From 3f6157004419e21547962670026c6f6001d06fe8 Mon Sep 17 00:00:00 2001 From: Channing Conger Date: Mon, 6 Jul 2026 16:58:05 -0700 Subject: [PATCH] [codex] bundle code mode host in release packages (#30202) ## Summary - build, strip, sign, and publish `codex-code-mode-host` with the primary Codex release binaries on Linux, macOS, and Windows - place the host beside `codex[.exe]` in canonical package archives, macOS DMGs, and the legacy Linux bundle so the runtime's sibling lookup succeeds - preserve and validate the host through standalone installers and Python runtime wheel staging - add package-builder coverage for source selection and the resulting package layout ## Why The process-owned code-mode client launches `codex-code-mode-host` as a sibling of the running Codex executable. Release artifacts currently build and bundle `codex` without that host, so code mode cannot start from installed packages. --- .../scripts/build-codex-package-archive.sh | 12 ++++ .github/workflows/rust-release-windows.yml | 10 +-- .github/workflows/rust-release.yml | 65 ++++++++++++------- scripts/codex_package/README.md | 7 +- scripts/codex_package/cargo.py | 14 +++- scripts/codex_package/cli.py | 14 ++++ scripts/codex_package/layout.py | 6 ++ scripts/codex_package/targets.py | 1 + scripts/codex_package/test_cargo.py | 21 ++++++ scripts/codex_package/test_layout.py | 56 ++++++++++++++++ scripts/install/install.ps1 | 1 + scripts/install/install.sh | 6 +- sdk/python/scripts/update_sdk_artifacts.py | 7 ++ .../test_artifact_workflow_and_binaries.py | 3 + 14 files changed, 193 insertions(+), 30 deletions(-) create mode 100644 scripts/codex_package/test_layout.py diff --git a/.github/scripts/build-codex-package-archive.sh b/.github/scripts/build-codex-package-archive.sh index 229213cc53..4592e78ce5 100644 --- a/.github/scripts/build-codex-package-archive.sh +++ b/.github/scripts/build-codex-package-archive.sh @@ -9,6 +9,7 @@ Usage: build-codex-package-archive.sh \ --entrypoint-dir \ --archive-dir \ [--bwrap-bin ] \ + [--code-mode-host-bin ] \ [--zsh-manifest ] \ [--codex-command-runner-bin ] \ [--codex-windows-sandbox-setup-bin ] \ @@ -23,6 +24,7 @@ archive_dir="" target_suffixed_entrypoint="false" resource_args=() bwrap_bin_provided="false" +code_mode_host_bin_provided="false" command_runner_bin_provided="false" sandbox_setup_bin_provided="false" @@ -49,6 +51,11 @@ while [[ $# -gt 0 ]]; do bwrap_bin_provided="true" shift 2 ;; + --code-mode-host-bin) + resource_args+=(--code-mode-host-bin "${2:?--code-mode-host-bin requires a value}") + code_mode_host_bin_provided="true" + shift 2 + ;; --zsh-manifest) resource_args+=(--zsh-manifest "${2:?--zsh-manifest requires a value}") shift 2 @@ -114,6 +121,11 @@ case "$target" in ;; esac +code_mode_host_bin="${entrypoint_dir%/}/codex-code-mode-host${exe_suffix}" +if [[ "$code_mode_host_bin_provided" == "false" && -f "$code_mode_host_bin" ]]; then + resource_args+=(--code-mode-host-bin "$code_mode_host_bin") +fi + entrypoint_name="$entrypoint" if [[ "$target_suffixed_entrypoint" == "true" ]]; then entrypoint_name="${entrypoint_name}-${target}" diff --git a/.github/workflows/rust-release-windows.yml b/.github/workflows/rust-release-windows.yml index 7069b6ca8f..31d832fcbe 100644 --- a/.github/workflows/rust-release-windows.yml +++ b/.github/workflows/rust-release-windows.yml @@ -7,7 +7,7 @@ on: # nested submodules. Prefer the system git CLI across every Cargo invocation. env: CARGO_NET_GIT_FETCH_WITH_CLI: "true" - WINDOWS_BINARIES: "codex codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner codex-app-server" + WINDOWS_BINARIES: "codex codex-code-mode-host codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner codex-app-server" jobs: build-windows-binaries: @@ -28,14 +28,14 @@ jobs: - runner: windows-x64 target: x86_64-pc-windows-msvc bundle: primary - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" runs_on: group: ${{ github.event.repository.name }}-runners labels: ${{ github.event.repository.name }}-windows-x64 - runner: windows-arm64 target: aarch64-pc-windows-msvc bundle: primary - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" runs_on: group: ${{ github.event.repository.name }}-runners labels: ${{ github.event.repository.name }}-windows-arm64 @@ -56,14 +56,14 @@ jobs: - runner: windows-x64 target: x86_64-pc-windows-msvc bundle: app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" runs_on: group: ${{ github.event.repository.name }}-runners labels: ${{ github.event.repository.name }}-windows-x64 - runner: windows-arm64 target: aarch64-pc-windows-msvc bundle: app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" runs_on: group: ${{ github.event.repository.name }}-runners labels: ${{ github.event.repository.name }}-windows-arm64 diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 49d24abc18..93f03e63d1 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -83,50 +83,50 @@ jobs: target: aarch64-apple-darwin bundle: primary artifact_name: aarch64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" build_dmg: "true" - runner: macos-15-xlarge target: aarch64-apple-darwin bundle: app-server artifact_name: aarch64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" build_dmg: "false" - runner: macos-15-xlarge target: x86_64-apple-darwin bundle: primary artifact_name: x86_64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" build_dmg: "true" - runner: macos-15-xlarge target: x86_64-apple-darwin bundle: app-server artifact_name: x86_64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" build_dmg: "false" # Release artifacts intentionally ship MUSL-linked Linux binaries. - runner: ${{ github.event.repository.name }}-linux-x64-xl target: x86_64-unknown-linux-musl bundle: primary artifact_name: x86_64-unknown-linux-musl - binaries: "codex codex-responses-api-proxy bwrap" + binaries: "codex codex-code-mode-host codex-responses-api-proxy bwrap" build_dmg: "false" - runner: ${{ github.event.repository.name }}-linux-x64-xl target: x86_64-unknown-linux-musl bundle: app-server artifact_name: x86_64-unknown-linux-musl-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" build_dmg: "false" - runner: ${{ github.event.repository.name }}-linux-arm64 target: aarch64-unknown-linux-musl bundle: primary artifact_name: aarch64-unknown-linux-musl - binaries: "codex codex-responses-api-proxy bwrap" + binaries: "codex codex-code-mode-host codex-responses-api-proxy bwrap" build_dmg: "false" - runner: ${{ github.event.repository.name }}-linux-arm64 target: aarch64-unknown-linux-musl bundle: app-server artifact_name: aarch64-unknown-linux-musl-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" build_dmg: "false" steps: @@ -335,6 +335,11 @@ jobs: mkdir -p "$dest" for binary in ${{ matrix.binaries }}; do + # Both variants package the host, but only the primary bundle publishes + # standalone binary archives to avoid duplicate release asset names. + if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then + continue + fi cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}" if [[ "${{ matrix.target }}" == *linux* ]]; then cp "target/${{ matrix.target }}/release/${binary}.sigstore" \ @@ -347,9 +352,13 @@ jobs: rm -rf "$bundle_root" mkdir -p "$bundle_root/codex-resources" cp "$dest/codex-${{ matrix.target }}" "$bundle_root/codex" + cp "$dest/codex-code-mode-host-${{ matrix.target }}" "$bundle_root/codex-code-mode-host" cp "$dest/bwrap-${{ matrix.target }}" "$bundle_root/codex-resources/bwrap" - chmod 0755 "$bundle_root/codex" "$bundle_root/codex-resources/bwrap" - tar -C "$bundle_root" -cf - codex codex-resources/bwrap | + chmod 0755 \ + "$bundle_root/codex" \ + "$bundle_root/codex-code-mode-host" \ + "$bundle_root/codex-resources/bwrap" + tar -C "$bundle_root" -cf - codex codex-code-mode-host codex-resources/bwrap | zstd -T0 -19 -o "$dest/codex-${{ matrix.target }}-bundle.tar.zst" fi @@ -497,19 +506,19 @@ jobs: - target: aarch64-apple-darwin bundle: primary artifact_name: aarch64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" - target: aarch64-apple-darwin bundle: app-server artifact_name: aarch64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" - target: x86_64-apple-darwin bundle: primary artifact_name: x86_64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" - target: x86_64-apple-darwin bundle: app-server artifact_name: x86_64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -615,22 +624,22 @@ jobs: - target: aarch64-apple-darwin bundle: primary artifact_name: aarch64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" build_dmg: "true" - target: aarch64-apple-darwin bundle: app-server artifact_name: aarch64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" build_dmg: "false" - target: x86_64-apple-darwin bundle: primary artifact_name: x86_64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" build_dmg: "true" - target: x86_64-apple-darwin bundle: app-server artifact_name: x86_64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" build_dmg: "false" steps: @@ -707,6 +716,11 @@ jobs: mkdir -p "$dest" for binary in ${{ matrix.binaries }}; do + # Both variants package the host, but only the primary bundle publishes + # standalone binary archives to avoid duplicate release asset names. + if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then + continue + fi cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}" done @@ -911,22 +925,22 @@ jobs: - target: aarch64-apple-darwin bundle: primary artifact_name: aarch64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" verify_dmg: "true" - target: aarch64-apple-darwin bundle: app-server artifact_name: aarch64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" verify_dmg: "false" - target: x86_64-apple-darwin bundle: primary artifact_name: x86_64-apple-darwin - binaries: "codex codex-responses-api-proxy" + binaries: "codex codex-code-mode-host codex-responses-api-proxy" verify_dmg: "true" - target: x86_64-apple-darwin bundle: app-server artifact_name: x86_64-apple-darwin-app-server - binaries: "codex-app-server" + binaries: "codex-app-server codex-code-mode-host" verify_dmg: "false" steps: @@ -983,6 +997,12 @@ jobs: binary_path="${RUNNER_TEMP}/signed-binaries/${binary}" verify_signed_binary "$binary_path" + # The app-server package contains the host, but its standalone archives + # are omitted above to avoid duplicate release asset names. + if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then + continue + fi + direct_archive_dir="${RUNNER_TEMP}/direct-archive-${binary}-${target}" rm -rf "$direct_archive_dir" mkdir -p "$direct_archive_dir" @@ -1014,6 +1034,7 @@ jobs: mkdir -p "$package_dir" tar -xzf "${packaged_dir}/${package_stem}-${target}.tar.gz" -C "$package_dir" verify_signed_binary "${package_dir}/bin/${package_entrypoint}" + verify_signed_binary "${package_dir}/bin/codex-code-mode-host" if [[ "${{ matrix.verify_dmg }}" != "true" ]]; then exit 0 diff --git a/scripts/codex_package/README.md b/scripts/codex_package/README.md index b76c18c3c2..7850bbd354 100644 --- a/scripts/codex_package/README.md +++ b/scripts/codex_package/README.md @@ -10,7 +10,8 @@ The builder creates a canonical Codex package directory: . ├── codex-package.json ├── bin -│ └── [.exe] +│ ├── [.exe] +│ └── codex-code-mode-host[.exe] ├── codex-resources │ ├── bwrap # Linux only │ ├── zsh/bin/zsh # supported Unix targets only @@ -40,6 +41,7 @@ grouped `cargo build` command per package when they are needed and no prebuilt override was provided: - all targets: the selected entrypoint, unless `--entrypoint-bin` is provided +- all targets: `codex-code-mode-host`, unless `--code-mode-host-bin` is provided - Linux targets: `bwrap`, unless `--bwrap-bin` is provided - Windows targets: `codex-command-runner` and `codex-windows-sandbox-setup`, unless the corresponding prebuilt helper flags are provided @@ -50,6 +52,9 @@ explicit target. Release jobs that already built and signed/notarized the entrypoint should pass `--entrypoint-bin` so the package contains that exact binary instead of rebuilding it. +Release jobs should likewise pass `--code-mode-host-bin` so the package contains +the signed host executable beside the signed entrypoint. + Release jobs that already built package resource binaries should also pass the corresponding resource flags: `--bwrap-bin` for Linux packages, and `--codex-command-runner-bin` plus `--codex-windows-sandbox-setup-bin` for diff --git a/scripts/codex_package/cargo.py b/scripts/codex_package/cargo.py index 208d85d174..f759e44762 100644 --- a/scripts/codex_package/cargo.py +++ b/scripts/codex_package/cargo.py @@ -17,6 +17,7 @@ CODEX_RS_ROOT = REPO_ROOT / "codex-rs" @dataclass(frozen=True) class SourceBuildOutputs: entrypoint_bin: Path + code_mode_host_bin: Path bwrap_bin: Path | None codex_command_runner_bin: Path | None codex_windows_sandbox_setup_bin: Path | None @@ -29,6 +30,7 @@ def build_source_binaries( cargo: str, profile: str, entrypoint_bin: Path | None, + code_mode_host_bin: Path | None, bwrap_bin: Path | None, codex_command_runner_bin: Path | None, codex_windows_sandbox_setup_bin: Path | None, @@ -43,6 +45,7 @@ def build_source_binaries( spec, variant, build_entrypoint=entrypoint_bin is None, + build_code_mode_host=code_mode_host_bin is None, build_bwrap=spec.is_linux and bwrap_bin is None, build_codex_command_runner=spec.is_windows and codex_command_runner_bin is None, build_codex_windows_sandbox_setup=spec.is_windows @@ -61,7 +64,7 @@ def build_source_binaries( cmd.extend(["--bin", binary]) cargo_env = None - if entrypoint_bin is None: + if entrypoint_bin is None or code_mode_host_bin is None: codex_v8_env = resolve_codex_v8_cargo_env(spec) if codex_v8_env: cargo_env = {**os.environ, **codex_v8_env} @@ -80,6 +83,11 @@ def build_source_binaries( entrypoint_bin, output_dir / variant.entrypoint_name(spec), ), + code_mode_host_bin=( + code_mode_host_bin.resolve() + if code_mode_host_bin is not None + else output_dir / f"codex-code-mode-host{spec.exe_suffix}" + ), bwrap_bin=resolve_output_path( bwrap_bin, output_dir / "bwrap" if spec.is_linux else None, @@ -102,6 +110,7 @@ def source_binaries_for_target( variant: PackageVariant, *, build_entrypoint: bool, + build_code_mode_host: bool, build_bwrap: bool, build_codex_command_runner: bool, build_codex_windows_sandbox_setup: bool, @@ -109,6 +118,8 @@ def source_binaries_for_target( binaries = [] if build_entrypoint: binaries.append(variant.cargo_bin) + if build_code_mode_host: + binaries.append("codex-code-mode-host") if build_bwrap: binaries.append("bwrap") if build_codex_command_runner: @@ -174,6 +185,7 @@ def cargo_profile_dirname(profile: str) -> str: def validate_source_outputs(outputs: SourceBuildOutputs) -> None: for path in [ outputs.entrypoint_bin, + outputs.code_mode_host_bin, outputs.bwrap_bin, outputs.codex_command_runner_bin, outputs.codex_windows_sandbox_setup_bin, diff --git a/scripts/codex_package/cli.py b/scripts/codex_package/cli.py index 1dd276827e..f461d0df43 100644 --- a/scripts/codex_package/cli.py +++ b/scripts/codex_package/cli.py @@ -82,6 +82,14 @@ def parse_args() -> argparse.Namespace: "variant. If omitted, the entrypoint is built with Cargo." ), ) + parser.add_argument( + "--code-mode-host-bin", + type=Path, + help=( + "Optional prebuilt codex-code-mode-host executable. If omitted, " + "the host is built with Cargo." + ), + ) parser.add_argument( "--bwrap-bin", type=Path, @@ -148,6 +156,11 @@ def main() -> int: "prebuilt entrypoint executable", "--entrypoint-bin", ), + code_mode_host_bin=resolve_optional_input_path( + args.code_mode_host_bin, + "prebuilt code-mode host executable", + "--code-mode-host-bin", + ), bwrap_bin=resolve_optional_input_path( args.bwrap_bin, "prebuilt Linux bwrap executable", @@ -167,6 +180,7 @@ def main() -> int: version = read_workspace_version() inputs = PackageInputs( entrypoint_bin=source_outputs.entrypoint_bin, + code_mode_host_bin=source_outputs.code_mode_host_bin, rg_bin=resolve_rg_bin(spec, args.rg_bin), zsh_bin=resolve_zsh_bin(spec, args.zsh_manifest), bwrap_bin=source_outputs.bwrap_bin, diff --git a/scripts/codex_package/layout.py b/scripts/codex_package/layout.py index 63598672ea..f4fef6912c 100644 --- a/scripts/codex_package/layout.py +++ b/scripts/codex_package/layout.py @@ -51,6 +51,11 @@ def build_package_dir( bin_dir / entrypoint_name, is_windows=spec.is_windows, ) + copy_executable( + inputs.code_mode_host_bin, + bin_dir / f"codex-code-mode-host{spec.exe_suffix}", + is_windows=spec.is_windows, + ) copy_executable(inputs.rg_bin, path_dir / spec.rg_name, is_windows=spec.is_windows) if inputs.zsh_bin is not None: @@ -130,6 +135,7 @@ def validate_package_dir( required_files = [ Path("bin") / variant.entrypoint_name(spec), + Path("bin") / f"codex-code-mode-host{spec.exe_suffix}", Path("codex-path") / spec.rg_name, ] executable_files = list(required_files) diff --git a/scripts/codex_package/targets.py b/scripts/codex_package/targets.py index 8307a3e630..016202cf5f 100644 --- a/scripts/codex_package/targets.py +++ b/scripts/codex_package/targets.py @@ -39,6 +39,7 @@ class PackageVariant: @dataclass(frozen=True) class PackageInputs: entrypoint_bin: Path + code_mode_host_bin: Path rg_bin: Path zsh_bin: Path | None bwrap_bin: Path | None diff --git a/scripts/codex_package/test_cargo.py b/scripts/codex_package/test_cargo.py index 6185f8b8b3..1a4a572b9b 100644 --- a/scripts/codex_package/test_cargo.py +++ b/scripts/codex_package/test_cargo.py @@ -20,6 +20,7 @@ class SourceBinariesForTargetTest(unittest.TestCase): TARGET_SPECS["aarch64-apple-darwin"], PACKAGE_VARIANTS["codex"], build_entrypoint=False, + build_code_mode_host=False, build_bwrap=False, build_codex_command_runner=False, build_codex_windows_sandbox_setup=False, @@ -35,6 +36,7 @@ class SourceBinariesForTargetTest(unittest.TestCase): TARGET_SPECS["x86_64-unknown-linux-musl"], PACKAGE_VARIANTS["codex"], build_entrypoint=False, + build_code_mode_host=False, build_bwrap=False, build_codex_command_runner=False, build_codex_windows_sandbox_setup=False, @@ -50,6 +52,7 @@ class SourceBinariesForTargetTest(unittest.TestCase): TARGET_SPECS["x86_64-pc-windows-msvc"], PACKAGE_VARIANTS["codex"], build_entrypoint=False, + build_code_mode_host=False, build_bwrap=False, build_codex_command_runner=False, build_codex_windows_sandbox_setup=False, @@ -63,6 +66,7 @@ class SourceBinariesForTargetTest(unittest.TestCase): TARGET_SPECS["x86_64-pc-windows-msvc"], PACKAGE_VARIANTS["codex"], build_entrypoint=False, + build_code_mode_host=False, build_bwrap=False, build_codex_command_runner=True, build_codex_windows_sandbox_setup=True, @@ -70,10 +74,25 @@ class SourceBinariesForTargetTest(unittest.TestCase): ["codex-command-runner", "codex-windows-sandbox-setup"], ) + def test_missing_code_mode_host_is_built_for_app_server(self) -> None: + self.assertEqual( + source_binaries_for_target( + TARGET_SPECS["aarch64-apple-darwin"], + PACKAGE_VARIANTS["codex-app-server"], + build_entrypoint=False, + build_code_mode_host=True, + build_bwrap=False, + build_codex_command_runner=False, + build_codex_windows_sandbox_setup=False, + ), + ["codex-code-mode-host"], + ) + def test_build_uses_prebuilt_windows_helpers_without_running_cargo(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: root = Path(temp_dir) entrypoint = touch_file(root / "codex.exe") + code_mode_host = touch_file(root / "codex-code-mode-host.exe") command_runner = touch_file(root / "codex-command-runner.exe") sandbox_setup = touch_file(root / "codex-windows-sandbox-setup.exe") @@ -83,12 +102,14 @@ class SourceBinariesForTargetTest(unittest.TestCase): cargo=str(root / "cargo-that-should-not-run"), profile="release", entrypoint_bin=entrypoint, + code_mode_host_bin=code_mode_host, bwrap_bin=None, codex_command_runner_bin=command_runner, codex_windows_sandbox_setup_bin=sandbox_setup, ) self.assertEqual(outputs.entrypoint_bin, entrypoint) + self.assertEqual(outputs.code_mode_host_bin, code_mode_host) self.assertEqual(outputs.codex_command_runner_bin, command_runner) self.assertEqual(outputs.codex_windows_sandbox_setup_bin, sandbox_setup) diff --git a/scripts/codex_package/test_layout.py b/scripts/codex_package/test_layout.py new file mode 100644 index 0000000000..157bce5e9c --- /dev/null +++ b/scripts/codex_package/test_layout.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import sys +import tempfile +import unittest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from codex_package.layout import build_package_dir +from codex_package.layout import validate_package_dir +from codex_package.targets import PACKAGE_VARIANTS +from codex_package.targets import PackageInputs +from codex_package.targets import TARGET_SPECS + + +class PackageLayoutTest(unittest.TestCase): + def test_app_server_package_places_code_mode_host_beside_entrypoint(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + package_dir = root / "package" + package_dir.mkdir() + inputs = PackageInputs( + entrypoint_bin=touch_executable(root / "codex-app-server"), + code_mode_host_bin=touch_executable(root / "codex-code-mode-host"), + rg_bin=touch_executable(root / "rg"), + zsh_bin=None, + bwrap_bin=touch_executable(root / "bwrap"), + codex_command_runner_bin=None, + codex_windows_sandbox_setup_bin=None, + ) + + build_package_dir( + package_dir, + "1.2.3", + PACKAGE_VARIANTS["codex-app-server"], + TARGET_SPECS["x86_64-unknown-linux-musl"], + inputs, + ) + validate_package_dir( + package_dir, + PACKAGE_VARIANTS["codex-app-server"], + TARGET_SPECS["x86_64-unknown-linux-musl"], + include_zsh=False, + ) + + self.assertTrue((package_dir / "bin" / "codex-code-mode-host").is_file()) + + +def touch_executable(path: Path) -> Path: + path.touch(mode=0o755) + return path + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 index 986f6c6566..e27d34b223 100644 --- a/scripts/install/install.ps1 +++ b/scripts/install/install.ps1 @@ -538,6 +538,7 @@ function Test-PackageContentsAreComplete { $expectedFiles = @( "codex-package.json", "bin\codex.exe", + "bin\codex-code-mode-host.exe", "codex-path\rg.exe", "codex-resources\codex-command-runner.exe", "codex-resources\codex-windows-sandbox-setup.exe" diff --git a/scripts/install/install.sh b/scripts/install/install.sh index d44b353860..12b66f958a 100755 --- a/scripts/install/install.sh +++ b/scripts/install/install.sh @@ -680,7 +680,10 @@ install_package_release() { rm -rf "$stage_release" mkdir -p "$stage_release" tar -xzf "$archive_path" -C "$stage_release" - chmod 0755 "$stage_release/bin/codex" "$stage_release/codex-path/rg" + chmod 0755 \ + "$stage_release/bin/codex" \ + "$stage_release/bin/codex-code-mode-host" \ + "$stage_release/codex-path/rg" if [ -f "$stage_release/codex-resources/bwrap" ]; then chmod 0755 "$stage_release/codex-resources/bwrap" fi @@ -733,6 +736,7 @@ release_dir_is_complete() { package) [ -f "$release_dir/codex-package.json" ] && [ -x "$release_dir/bin/codex" ] && + [ -x "$release_dir/bin/codex-code-mode-host" ] && [ -x "$release_dir/codex" ] && [ -x "$release_dir/codex-path/rg" ] || return 1 diff --git a/sdk/python/scripts/update_sdk_artifacts.py b/sdk/python/scripts/update_sdk_artifacts.py index f3b97c6b6f..1c6ec59402 100755 --- a/sdk/python/scripts/update_sdk_artifacts.py +++ b/sdk/python/scripts/update_sdk_artifacts.py @@ -53,6 +53,10 @@ def runtime_binary_name() -> str: return "codex.exe" if _is_windows() else "codex" +def runtime_code_mode_host_name() -> str: + return "codex-code-mode-host.exe" if _is_windows() else "codex-code-mode-host" + + def staged_runtime_package_root(root: Path) -> Path: return root / RUNTIME_PACKAGE_ROOT @@ -276,6 +280,9 @@ def _validate_codex_package_layout(package_dir: Path, package_archive: Path) -> package_binary = package_dir / "bin" / runtime_binary_name() if not package_binary.is_file(): missing_entries.append(str(Path("bin") / runtime_binary_name())) + code_mode_host = package_dir / "bin" / runtime_code_mode_host_name() + if not code_mode_host.is_file(): + missing_entries.append(str(Path("bin") / runtime_code_mode_host_name())) if missing_entries: missing = ", ".join(missing_entries) raise RuntimeError(f"Missing Codex package layout entries in {package_archive}: {missing}") diff --git a/sdk/python/tests/test_artifact_workflow_and_binaries.py b/sdk/python/tests/test_artifact_workflow_and_binaries.py index 301a75abac..ad1262e836 100644 --- a/sdk/python/tests/test_artifact_workflow_and_binaries.py +++ b/sdk/python/tests/test_artifact_workflow_and_binaries.py @@ -57,6 +57,7 @@ def _write_fake_codex_package(package_dir: Path, script) -> Path: (package_dir / "codex-path").mkdir() (package_dir / "codex-package.json").write_text('{"variant":"codex"}\n') (package_dir / "bin" / script.runtime_binary_name()).write_text("fake codex\n") + (package_dir / "bin" / script.runtime_code_mode_host_name()).write_text("fake code mode host\n") (package_dir / "codex-resources" / "bwrap").write_text("fake bwrap\n") (package_dir / "codex-path" / "rg").write_text("fake rg\n") return package_dir @@ -682,11 +683,13 @@ def test_stage_runtime_release_copies_package_layout_and_sets_version( assert { "metadata": (package_root / "codex-package.json").read_text(), "codex": (package_root / "bin" / script.runtime_binary_name()).read_text(), + "code_mode_host": (package_root / "bin" / script.runtime_code_mode_host_name()).read_text(), "bwrap": (package_root / "codex-resources" / "bwrap").read_text(), "rg": (package_root / "codex-path" / "rg").read_text(), } == { "metadata": '{"variant":"codex"}\n', "codex": "fake codex\n", + "code_mode_host": "fake code mode host\n", "bwrap": "fake bwrap\n", "rg": "fake rg\n", }