[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.
This commit is contained in:
Channing Conger
2026-07-06 16:58:05 -07:00
committed by GitHub
parent 9732b406e7
commit 3f61570044
14 changed files with 193 additions and 30 deletions

View File

@@ -10,7 +10,8 @@ The builder creates a canonical Codex package directory:
.
├── codex-package.json
├── bin
── <entrypoint>[.exe]
── <entrypoint>[.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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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"

View File

@@ -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