Ship codex-exec-server in CLI and app-server packages (#57590)

## What changed

- Build and bundle the standalone executor in `bin/` for both package variants, and advertise its path through `execServer` in `codex-package.json`.
- Include the executor in Cargo and Bazel release packaging, macOS signing and notarization, and Windows signing verification. Preserve prebuilt signed binaries during packaging.
- Make installers detect a missing advertised executor while continuing to accept older packages without it.

## Testing

Add coverage for executor archive contents and executable permissions, signed binary preservation, and macOS entitlement verification. Add package smoke tests for initialization, version reporting, filesystem and process sandbox enforcement, and startup error parity with `codex exec-server`.

GitOrigin-RevId: 5c0027fc19f16cf9c2a5bb52dbd065a9776bc2af
This commit is contained in:
Dmitriy Kovalenko
2026-10-27 23:14:18 +00:00
committed by copyberry
parent a9ea49e78b
commit cf2c04b325
23 changed files with 497 additions and 55 deletions
@@ -23,7 +23,7 @@ runs:
shell: bash
working-directory: codex-rs
env:
BASE_BINARIES: "codex codex-app-server codex-code-mode-host"
BASE_BINARIES: "codex codex-app-server codex-code-mode-host codex-exec-server"
WINDOWS_ONLY_BINARIES: "codex-command-runner codex-windows-sandbox-setup"
BUILD_MODE: ${{ inputs.build-mode }}
BUILD_SYSTEM: ${{ inputs.build-system }}
@@ -138,6 +138,7 @@ runs:
package_args=(
--target "$target"
--code-mode-host-bin "${release_dir}/codex-code-mode-host${exe_suffix}"
--exec-server-bin "${release_dir}/codex-exec-server${exe_suffix}"
--cargo-profile release
--package-version "$CODEX_PACKAGE_VERSION"
--force
@@ -183,7 +184,6 @@ runs:
mkdir -p "${GITHUB_WORKSPACE}/codex-rs/target"
pytest_args=(
-v
--basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-tmp"
--package-target "$target"
--symbols-archive "${GITHUB_WORKSPACE}/symbols-dist/${target}/codex-symbols-${target}.tar.gz"
)
@@ -194,13 +194,17 @@ runs:
exit 1
fi
# Windows setup helpers can finish background ACL work after pytest exits.
# Separate roots keep the next run from deleting their executables.
uv run --frozen pytest "${pytest_args[@]}" \
--basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-gzip-tmp" \
--compression gzip \
--cli-archive "${archive_dir}/codex-package-${target}.tar.gz" \
--app-server-archive "${archive_dir}/codex-app-server-package-${target}.tar.gz" \
test_codex_package.py
uv run --frozen pytest "${pytest_args[@]}" \
--basetemp "${GITHUB_WORKSPACE}/codex-rs/target/package-smoke-zstd-tmp" \
--compression zstd \
--cli-archive "${archive_dir}/codex-package-${target}.tar.zst" \
--app-server-archive "${archive_dir}/codex-app-server-package-${target}.tar.zst" \
@@ -136,6 +136,8 @@ case "$target" in
;;
esac
resource_args+=(--exec-server-bin "${entrypoint_dir%/}/codex-exec-server${exe_suffix}")
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")
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -19,7 +19,7 @@ CODE_SIGNING_ID = "codex"
APP = Path("CodexCLI.app")
EXECUTABLE = APP / "Contents/MacOS/codex"
SIGNING = Path(__file__).resolve().parent
HELPERS = ("bin/codex-code-mode-host", "codex-path/rg")
HELPERS = ("bin/codex-code-mode-host", "bin/codex-exec-server", "codex-path/rg")
LAUNCHER = """#!/bin/sh
set -eu
entry="$0"
@@ -2,6 +2,7 @@
import argparse
import os
import plistlib
import subprocess
import sys
from pathlib import Path
@@ -134,6 +135,12 @@ def main(configuration: bundle.ProfileConfiguration | None = None):
check=True,
)
if relative.startswith("bin/"):
# Signing tools may omit the blob when no entitlements are requested.
if (
actual.stat().st_size == 0
and plistlib.loads(entitlements.read_bytes()) == {}
):
actual.write_bytes(entitlements.read_bytes())
expected = reports / f"{name}-expected.plist"
subprocess.run(
[
@@ -34,10 +34,15 @@ if name == "codesign":
target = Path(args[-1]).name
if target == "CodexCLI.app":
source = Path(os.environ["CODEX_REPO_ROOT"]) / "signing-verification/codex-provisioned-entitlements.plist"
elif target in ("codex", "codex-code-mode-host"):
elif target in ("codex", "codex-code-mode-host", "codex-exec-server"):
source = Path(os.environ["MOCK_SIGNING_SCRIPTS"]) / (target + ".entitlements.plist")
else:
sys.exit(0)
if target == "codex-exec-server" and os.environ.get("MOCK_EXECUTOR_ENTITLEMENTS"):
sys.stdout.buffer.write(plistlib.dumps({"unexpected": True}))
sys.exit(0)
if not plistlib.loads(source.read_bytes()) and os.environ.get("MOCK_OMIT_EMPTY_ENTITLEMENTS"):
sys.exit(0)
if os.environ.get("MOCK_WRONG_ENTITLEMENTS"):
sys.stdout.buffer.write(plistlib.dumps({"unexpected": True}))
else:
@@ -145,15 +150,21 @@ class MacosSigningTests(ProvisioningTestCase):
)
def test_standard_and_provisioned_signing_and_verification(self):
for provisioned, target in itertools.product(
(False, True), ("aarch64-apple-darwin", "x86_64-apple-darwin")
for provisioned, target, omit_empty in itertools.product(
(False, True),
("aarch64-apple-darwin", "x86_64-apple-darwin"),
(False, True),
):
with (
self.subTest(provisioned=provisioned, target=target),
self.subTest(
provisioned=provisioned, target=target, omit_empty=omit_empty
),
tempfile.TemporaryDirectory() as directory,
):
root = Path(directory).resolve()
package, driver, env = self.fixture(root, provisioned, target)
if omit_empty:
env["MOCK_OMIT_EMPTY_ENTITLEMENTS"] = "1"
result = self.run_driver(driver, "sign", package, env)
self.assertEqual(result.returncode, 0, result.stderr)
result = self.run_driver(driver, "verify", package, env)
@@ -189,13 +200,13 @@ class MacosSigningTests(ProvisioningTestCase):
)
self.assertEqual(
["--entitlements" in call for call in signing],
[True, True, False],
[True, True, True, False],
)
self.assertEqual(
[call[0] for call in calls if call[0].startswith("notarize")],
["notarize_with_akv.py"]
if provisioned
else ["notarize_macos_binary_with_akv.sh"] * 3,
else ["notarize_macos_binary_with_akv.sh"] * 4,
)
self.assertEqual(
[call[1:] for call in calls if call[0] == "lipo"],
@@ -318,7 +329,12 @@ class MacosSigningTests(ProvisioningTestCase):
def test_verification_rejects_wrong_signing_identity_and_entitlements(self):
for provisioned, failure in itertools.product(
(False, True), ("MOCK_WRONG_SIGNING_IDENTITY", "MOCK_WRONG_ENTITLEMENTS")
(False, True),
(
"MOCK_WRONG_SIGNING_IDENTITY",
"MOCK_WRONG_ENTITLEMENTS",
"MOCK_EXECUTOR_ENTITLEMENTS",
),
):
with (
self.subTest(provisioned=provisioned, failure=failure),
+25 -5
View File
@@ -4,7 +4,7 @@ on:
workflow_call:
env:
WINDOWS_BINARIES: "codex codex-code-mode-host codex-responses-api-proxy codex-windows-sandbox-setup codex-windows-sandbox-service codex-command-runner codex-app-server"
WINDOWS_BINARIES: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy codex-windows-sandbox-setup codex-windows-sandbox-service codex-command-runner codex-app-server"
jobs:
build-windows-binaries:
@@ -39,14 +39,14 @@ jobs:
- runner: windows-x64
target: x86_64-pc-windows-msvc
bundle: primary
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
runs_on:
group: ${{ github.event.repository.name }}-runners
labels: ${{ github.event.repository.name }}-windows-arm64
@@ -67,14 +67,14 @@ jobs:
- runner: windows-x64
target: x86_64-pc-windows-msvc
bundle: app-server
binaries: "codex-app-server codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
runs_on:
group: ${{ github.event.repository.name }}-runners
labels: ${{ github.event.repository.name }}-windows-arm64
@@ -549,6 +549,26 @@ jobs:
--entrypoint-dir "$CARGO_TARGET_DIR/$target/release" \
--archive-dir "dist/$target"
- name: Verify packaged executor signatures and bytes
shell: pwsh
run: |
$target = "${{ matrix.target }}"
$source = Join-Path $env:CARGO_TARGET_DIR "$target/release/codex-exec-server.exe"
foreach ($stem in @("codex-package", "codex-app-server-package")) {
$package = Join-Path $env:RUNNER_TEMP "verify-executor-$stem-$target"
New-Item -ItemType Directory -Path $package | Out-Null
tar -xzf "dist/$target/$stem-$target.tar.gz" -C $package
if ($LASTEXITCODE -ne 0) { throw "Cannot extract executor package" }
$binary = Join-Path $package "bin/codex-exec-server.exe"
if ((Get-FileHash $binary).Hash -ne (Get-FileHash $source).Hash) {
throw "Packaged executor differs from the signed input"
}
$signature = Get-AuthenticodeSignature $binary
if ($signature.Status -ne "Valid" -or $null -eq $signature.TimeStamperCertificate) {
throw "Invalid or untimestamped executor signature"
}
}
- name: Verify packaged Windows voice closure
shell: pwsh
run: |
+36 -26
View File
@@ -96,50 +96,50 @@ jobs:
target: aarch64-apple-darwin
bundle: primary
artifact_name: aarch64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
build_dmg: "false"
- runner: macos-15-xlarge
target: x86_64-apple-darwin
bundle: primary
artifact_name: x86_64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
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-code-mode-host codex-responses-api-proxy bwrap"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
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-code-mode-host codex-responses-api-proxy bwrap"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
build_dmg: "false"
steps:
@@ -398,9 +398,9 @@ jobs:
mkdir -p "$dest"
for binary in ${{ matrix.binaries }}; do
# Both variants package the host, but only the primary bundle publishes
# Both variants package the host and executor; only the primary publishes
# standalone binary archives to avoid duplicate release asset names.
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
if [[ "${{ matrix.bundle }}" == "app-server" && ( "$binary" == "codex-code-mode-host" || "$binary" == "codex-exec-server" ) ]]; then
continue
fi
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
@@ -662,19 +662,19 @@ jobs:
- target: aarch64-apple-darwin
bundle: primary
artifact_name: aarch64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
- target: aarch64-apple-darwin
bundle: app-server
artifact_name: aarch64-apple-darwin-app-server
binaries: "codex-app-server codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
- target: x86_64-apple-darwin
bundle: primary
artifact_name: x86_64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server codex-responses-api-proxy"
- target: x86_64-apple-darwin
bundle: app-server
artifact_name: x86_64-apple-darwin-app-server
binaries: "codex-app-server codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -907,22 +907,22 @@ jobs:
- target: aarch64-apple-darwin
bundle: primary
artifact_name: aarch64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
build_dmg: "false"
- target: x86_64-apple-darwin
bundle: primary
artifact_name: x86_64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
build_dmg: "false"
steps:
@@ -1001,9 +1001,9 @@ jobs:
mkdir -p "$dest"
for binary in ${{ matrix.binaries }}; do
# Both variants package the host, but only the primary bundle publishes
# Both variants package the host and executor; only the primary publishes
# standalone binary archives to avoid duplicate release asset names.
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
if [[ "${{ matrix.bundle }}" == "app-server" && ( "$binary" == "codex-code-mode-host" || "$binary" == "codex-exec-server" ) ]]; then
continue
fi
cp "target/${{ matrix.target }}/release/${binary}" "$dest/${binary}-${{ matrix.target }}"
@@ -1300,22 +1300,22 @@ jobs:
- target: aarch64-apple-darwin
bundle: primary
artifact_name: aarch64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
verify_dmg: "false"
- target: x86_64-apple-darwin
bundle: primary
artifact_name: x86_64-apple-darwin
binaries: "codex codex-code-mode-host codex-responses-api-proxy"
binaries: "codex codex-code-mode-host codex-exec-server 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 codex-code-mode-host"
binaries: "codex-app-server codex-code-mode-host codex-exec-server"
verify_dmg: "false"
steps:
@@ -1376,6 +1376,15 @@ jobs:
normalized_actual="$(mktemp)"
normalized_expected="$(mktemp)"
codesign -d --entitlements :- "$path" >"$actual_entitlements"
# An omitted entitlement blob is valid only when the expected plist is empty.
python3 - "$expected_entitlements" "$actual_entitlements" <<'PY'
import plistlib
import sys
from pathlib import Path
expected, actual = map(Path, sys.argv[1:])
if not actual.stat().st_size and plistlib.loads(expected.read_bytes()) == {}:
actual.write_bytes(expected.read_bytes())
PY
plutil -convert xml1 -o "$normalized_actual" "$actual_entitlements"
plutil -convert xml1 -o "$normalized_expected" "$expected_entitlements"
diff -u "$normalized_expected" "$normalized_actual"
@@ -1386,9 +1395,9 @@ jobs:
binary_path="${RUNNER_TEMP}/signed-binaries/${binary}"
verify_signed_binary "$binary_path" "$binary"
# The app-server package contains the host, but its standalone archives
# The app-server package contains both helpers, but their standalone archives
# are omitted above to avoid duplicate release asset names.
if [[ "${{ matrix.bundle }}" == "app-server" && "$binary" == "codex-code-mode-host" ]]; then
if [[ "${{ matrix.bundle }}" == "app-server" && ( "$binary" == "codex-code-mode-host" || "$binary" == "codex-exec-server" ) ]]; then
continue
fi
@@ -1424,6 +1433,7 @@ jobs:
tar -xzf "${packaged_dir}/${package_stem}-${target}.tar.gz" -C "$package_dir"
verify_signed_binary "${package_dir}/bin/${package_entrypoint}" "$package_entrypoint"
verify_signed_binary "${package_dir}/bin/codex-code-mode-host" "codex-code-mode-host"
verify_signed_binary "${package_dir}/bin/codex-exec-server" "codex-exec-server"
for resource in \
"${package_dir}/codex-path/rg"
+3
View File
@@ -53,6 +53,7 @@ runtime_archive(
"//codex-rs/app-server:release_files",
"//codex-rs/cli:release_files",
"//codex-rs/code-mode-host:release_files",
"//codex-rs/exec-server-cli:release_files",
] + select({
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
"@platforms//os:windows": [
@@ -70,6 +71,7 @@ runtime_archive(
srcs = [
"//codex-rs/cli:release_files",
"//codex-rs/code-mode-host:release_files",
"//codex-rs/exec-server-cli:release_files",
"//codex-rs/responses-api-proxy:release_files",
] + select({
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
@@ -84,6 +86,7 @@ runtime_archive(
srcs = [
"//codex-rs/app-server:release_files",
"//codex-rs/code-mode-host:release_files",
"//codex-rs/exec-server-cli:release_files",
] + select({
"@platforms//os:linux": ["//codex-rs/bwrap:release_files"],
"//conditions:default": [],
+6
View File
@@ -1,4 +1,6 @@
load("//:defs.bzl", "codex_rust_crate")
load("//bazel/platforms:release_binaries.bzl", "multiplatform_binaries")
load("//bazel/rules:runtime_package.bzl", "release_binary")
codex_rust_crate(
name = "exec-server-cli",
@@ -9,3 +11,7 @@ codex_rust_crate(
# The standalone integration test launches native OS sandboxes itself.
test_tags = ["no-sandbox"],
)
release_binary(binary = ":codex-exec-server")
multiplatform_binaries(name = "codex-exec-server")
+2
View File
@@ -17,6 +17,8 @@ use `codex-arg0` for hidden filesystem, process, and platform sandbox dispatch.
The standalone executable accepts the same executor arguments and `-c` config
overrides, including `--listen stdio`. Build it with
`cargo build -p codex-exec-server-cli --bin codex-exec-server`.
Canonical packages place it in `bin/` beside the entrypoint, with platform
sandbox resources in `codex-resources/`.
## Command metadata
+10 -5
View File
@@ -19,7 +19,8 @@ The builder creates a canonical Codex package directory:
├── codex-package.json
├── bin
│ ├── <entrypoint>[.exe]
│ └── codex-code-mode-host[.exe]
│ ├── codex-code-mode-host[.exe]
│ └── codex-exec-server[.exe]
├── codex-resources
│ ├── bwrap # Linux only
│ ├── codex-command-runner.exe # Windows only
@@ -30,6 +31,9 @@ The builder creates a canonical Codex package directory:
The package directory is the primary artifact. Archive formats such as
`.tar.gz`, `.tar.zst`, and `.zip` are serializations of that directory.
The `execServer` field in `codex-package.json` advertises the packaged executor.
Installers use its presence to repair a missing executor while accepting older
packages that predate the standalone binary.
If `--target` is omitted, the builder uses the release target for the current
host platform. On Linux, that default is a musl target to match Codex release
@@ -50,6 +54,7 @@ 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
- all targets: `codex-exec-server`, unless `--exec-server-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
@@ -60,15 +65,15 @@ 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 should likewise pass `--code-mode-host-bin` and `--exec-server-bin`
so the package preserves the signed host and executor beside the entrypoint.
On Linux and macOS, the builder strips symbols from the **package copies** of
source-built release-profile entrypoint and code-mode host binaries. Cargo outputs
source-built release-profile entrypoint, code-mode host, and executor binaries. Cargo outputs
retain their symbols for debugging. The default `--strip auto` also preserves
development/profiling builds and prebuilt inputs
byte-for-byte, including signatures. Use `--strip all` to also strip prebuilt
entrypoint/host copies, or `--strip none` to keep symbols in every package copy.
first-party executable copies, or `--strip none` to keep symbols in every package copy.
Strip before production signing; `--strip all` is not appropriate for inputs
whose release signatures must be preserved. Windows MSVC symbols are separate
PDB files and are not included in the package.
+17 -1
View File
@@ -18,6 +18,7 @@ CODEX_RS_ROOT = REPO_ROOT / "codex-rs"
class SourceBuildOutputs:
entrypoint_bin: Path
code_mode_host_bin: Path
exec_server_bin: Path
bwrap_bin: Path | None
codex_command_runner_bin: Path | None
codex_windows_sandbox_setup_bin: Path | None
@@ -31,6 +32,7 @@ def build_source_binaries(
profile: str,
entrypoint_bin: Path | None,
code_mode_host_bin: Path | None,
exec_server_bin: Path | None,
bwrap_bin: Path | None,
codex_command_runner_bin: Path | None,
codex_windows_sandbox_setup_bin: Path | None,
@@ -46,6 +48,7 @@ def build_source_binaries(
variant,
build_entrypoint=entrypoint_bin is None,
build_code_mode_host=code_mode_host_bin is None,
build_exec_server=exec_server_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
@@ -64,7 +67,11 @@ def build_source_binaries(
cmd.extend(["--bin", binary])
cargo_env = None
if entrypoint_bin is None or code_mode_host_bin is None:
if (
entrypoint_bin is None
or code_mode_host_bin is None
or exec_server_bin is None
):
codex_v8_env = resolve_codex_v8_cargo_env(spec)
if codex_v8_env:
cargo_env = {**os.environ, **codex_v8_env}
@@ -88,6 +95,11 @@ def build_source_binaries(
if code_mode_host_bin is not None
else output_dir / f"codex-code-mode-host{spec.exe_suffix}"
),
exec_server_bin=(
exec_server_bin.resolve()
if exec_server_bin is not None
else output_dir / f"codex-exec-server{spec.exe_suffix}"
),
bwrap_bin=resolve_output_path(
bwrap_bin,
output_dir / "bwrap" if spec.is_linux else None,
@@ -111,6 +123,7 @@ def source_binaries_for_target(
*,
build_entrypoint: bool,
build_code_mode_host: bool,
build_exec_server: bool,
build_bwrap: bool,
build_codex_command_runner: bool,
build_codex_windows_sandbox_setup: bool,
@@ -120,6 +133,8 @@ def source_binaries_for_target(
binaries.append(variant.cargo_bin)
if build_code_mode_host:
binaries.append("codex-code-mode-host")
if build_exec_server:
binaries.append("codex-exec-server")
if build_bwrap:
binaries.append("bwrap")
if build_codex_command_runner:
@@ -186,6 +201,7 @@ def validate_source_outputs(outputs: SourceBuildOutputs) -> None:
for path in [
outputs.entrypoint_bin,
outputs.code_mode_host_bin,
outputs.exec_server_bin,
outputs.bwrap_bin,
outputs.codex_command_runner_bin,
outputs.codex_windows_sandbox_setup_bin,
+12
View File
@@ -140,6 +140,11 @@ def create_parser() -> argparse.ArgumentParser:
"the host is built with Cargo."
),
)
parser.add_argument(
"--exec-server-bin",
type=Path,
help="Optional prebuilt codex-exec-server executable. If omitted, build it with Cargo.",
)
parser.add_argument(
"--bwrap-bin",
type=Path,
@@ -210,6 +215,11 @@ def assemble_package(args: argparse.Namespace) -> Path:
"prebuilt code-mode host executable",
"--code-mode-host-bin",
),
exec_server_bin=resolve_optional_input_path(
args.exec_server_bin,
"prebuilt executor executable",
"--exec-server-bin",
),
bwrap_bin=resolve_optional_input_path(
args.bwrap_bin,
"prebuilt Linux bwrap executable",
@@ -229,6 +239,7 @@ def assemble_package(args: argparse.Namespace) -> Path:
inputs = PackageInputs(
entrypoint_bin=source_outputs.entrypoint_bin,
code_mode_host_bin=source_outputs.code_mode_host_bin,
exec_server_bin=source_outputs.exec_server_bin,
rg_bin=resolve_rg_bin(spec, args.rg_bin),
bwrap_bin=source_outputs.bwrap_bin,
codex_command_runner_bin=source_outputs.codex_command_runner_bin,
@@ -239,6 +250,7 @@ def assemble_package(args: argparse.Namespace) -> Path:
for filename, prebuilt in (
(variant.entrypoint_name(spec), args.entrypoint_bin),
(f"codex-code-mode-host{spec.exe_suffix}", args.code_mode_host_bin),
(f"codex-exec-server{spec.exe_suffix}", args.exec_server_bin),
):
if args.strip == "all" or (
args.strip == "auto"
+8
View File
@@ -55,6 +55,11 @@ def build_package_dir(
bin_dir / f"codex-code-mode-host{spec.exe_suffix}",
is_windows=spec.is_windows,
)
copy_executable(
inputs.exec_server_bin,
bin_dir / f"codex-exec-server{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.bwrap_bin is not None:
@@ -80,6 +85,7 @@ def build_package_dir(
"target": spec.target,
"variant": variant.name,
"entrypoint": f"bin/{entrypoint_name}",
"execServer": f"bin/codex-exec-server{spec.exe_suffix}",
"resourcesDir": "codex-resources",
"pathDir": "codex-path",
}
@@ -113,6 +119,7 @@ def validate_package_dir(
"target": spec.target,
"variant": variant.name,
"entrypoint": f"bin/{variant.entrypoint_name(spec)}",
"execServer": f"bin/codex-exec-server{spec.exe_suffix}",
"resourcesDir": "codex-resources",
"pathDir": "codex-path",
}
@@ -126,6 +133,7 @@ def validate_package_dir(
required_files = [
Path("bin") / variant.entrypoint_name(spec),
Path("bin") / f"codex-code-mode-host{spec.exe_suffix}",
Path("bin") / f"codex-exec-server{spec.exe_suffix}",
Path("codex-path") / spec.rg_name,
]
executable_files = list(required_files)
@@ -208,7 +208,12 @@ def code_mode_host_debug_symbols(
tmp_path_factory: pytest.TempPathFactory,
) -> Path:
target = pytestconfig.getoption("package_target")
binaries = {"codex", "codex-app-server", "codex-code-mode-host"}
binaries = {
"codex",
"codex-app-server",
"codex-code-mode-host",
"codex-exec-server",
}
if "windows" in target:
binaries.update({"codex-command-runner", "codex-windows-sandbox-setup"})
@@ -4,7 +4,8 @@ ROOT_OF_EXTRACTED_PACKAGE
├── bin
│ ├── codex[.exe] # CLI package only
│ ├── codex-app-server[.exe] # app-server package only
│ └── codex-code-mode-host[.exe]
│ ├── codex-code-mode-host[.exe]
│ └── codex-exec-server[.exe]
├── codex-package.json
├── codex-path
│ └── rg[.exe]
@@ -18,11 +19,13 @@ Each package contains one entrypoint, not both codex and codex-app-server.
"""
import json
import os
import re
import shutil
import subprocess
import sys
import tomllib
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import pytest
@@ -67,6 +70,261 @@ def test_cli_version_matches_release_sources(package: SmokePackage) -> None:
assert package.run("--version").stdout == f"codex-cli {version}\n"
@pytest.mark.parametrize("variant", ["cli_root", "app_server_root"])
def test_packaged_executor_initializes_and_enforces_sandbox(
package: SmokePackage, variant: str
) -> None:
"""Each package re-enters its executor helpers and enforces filesystem/process policy."""
root = getattr(package, variant)
windows = "windows" in package.target
suffix = ".exe" if windows else ""
executor = root / "bin" / f"codex-exec-server{suffix}"
directory = package.directory / f"{variant}-executor-sandbox"
directory.mkdir()
workspace = directory / "workspace"
workspace.mkdir()
home = directory / "home"
home.mkdir()
blocked = directory / "blocked.txt"
blocked.write_text("unchanged")
environment = {**package.environment, "CODEX_HOME": str(home)}
if windows:
# Elevated mode must discover the setup/runner in this package's resources.
# Exclude build outputs and host Codex installations from helper fallback.
environment["PATH"] = str(Path(os.environ["SystemRoot"]) / "System32")
sandbox = {
"permissions": {
"type": "managed",
"file_system": {
"type": "restricted",
"entries": [
{
"path": {"type": "special", "value": {"kind": "root"}},
"access": "read",
},
{
"path": {"type": "path", "path": workspace.as_uri()},
"access": "write",
},
],
},
"network": "restricted",
},
"cwd": workspace.as_uri(),
"windowsSandboxLevel": "elevated" if windows else "disabled",
}
version = (
subprocess.check_output(
[str(executor), "--version"], env=package.environment, text=True, timeout=45
)
.strip()
.split()[-1]
)
assert version == package.run("--version").stdout.strip().split()[-1]
with (
ThreadPoolExecutor(max_workers=1) as reader,
subprocess.Popen(
[str(executor), "--listen", "stdio"],
cwd=package.directory,
env=environment,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
) as process,
):
assert process.stdin is not None and process.stdout is not None
try:
process.stdin.write(
json.dumps(
{
"id": 1,
"method": "initialize",
"params": {"clientName": "package-smoke"},
}
)
+ "\n"
)
process.stdin.flush()
initialized = json.loads(
reader.submit(process.stdout.readline).result(timeout=45)
)
assert initialized["id"] == 1
assert initialized["result"]["sessionId"]
reported_environment = initialized["result"]["environmentInfo"]
metadata = json.loads((root / "codex-package.json").read_text())
assert reported_environment["executorVersion"] == metadata["version"]
process.stdin.write('{"method":"initialized","params":{}}\n')
# Restricted writes always launch the filesystem helper, even when allowed.
for request_id, path in [(2, workspace / "allowed.txt"), (3, blocked)]:
process.stdin.write(
json.dumps(
{
"id": request_id,
"method": "fs/writeFile",
"params": {
"path": path.as_uri(),
"dataBase64": "b2s=",
"sandbox": sandbox,
},
}
)
+ "\n"
)
process.stdin.flush()
# The first Windows helper can provision sandbox accounts before replying.
response = json.loads(
reader.submit(process.stdout.readline).result(
timeout=180 if windows and request_id == 2 else 45
)
)
if request_id == 2:
assert response == {"id": 2, "result": {}}, response
assert path.read_text() == "ok"
else:
assert response["id"] == 3 and "error" in response, response
assert blocked.read_text() == "unchanged"
command = (
[
str(Path(os.environ["SystemRoot"]) / "System32" / "cmd.exe"),
"/D",
"/S",
"/C",
r"echo process>process.txt & echo forbidden>..\blocked.txt & exit /b 0",
]
if windows
else [
"/bin/sh",
"-c",
"printf process >process.txt; printf forbidden >../blocked.txt; exit 0",
]
)
process.stdin.write(
json.dumps(
{
"id": 4,
"method": "process/start",
"params": {
"processId": "package-sandbox",
"argv": command,
"cwd": workspace.as_uri(),
"env": {
key: value
for key, value in environment.items()
if key.casefold() in {"path", "systemroot"}
},
"sandbox": sandbox,
"tty": False,
"pipeStdin": False,
"arg0": None,
},
}
)
+ "\n"
)
process.stdin.flush()
started = False
exit_code = None
notifications = []
while not started or exit_code is None:
response = json.loads(
reader.submit(process.stdout.readline).result(timeout=45)
)
notifications.append(response)
if response.get("id") == 4:
expected_type = (
"windowsRestrictedToken"
if windows
else "macosSeatbelt"
if "apple-darwin" in package.target
else "linuxSeccomp"
)
assert response["result"]["sandboxType"] == expected_type, response
started = True
if response.get("method") == "process/exited":
exit_code = response["params"]["exitCode"]
assert exit_code == 0, notifications
assert (workspace / "process.txt").is_file(), notifications
assert (workspace / "process.txt").read_text().strip() == "process"
assert blocked.read_text() == "unchanged"
process.stdin.close()
assert process.wait(timeout=45) == 0
finally:
if process.poll() is None:
process.kill()
def test_packaged_executor_matches_subcommand_startup(
package: SmokePackage,
tmp_path: Path,
) -> None:
"""Both binaries reject the same invalid executor arguments and config."""
home = tmp_path / "home"
temporary = tmp_path / "tmp"
home.mkdir()
temporary.mkdir()
(home / "config.toml").write_text("unknown_executor_option = true\n")
# Release builds require the helper directory to live outside their temp root.
environment = {
**package.environment,
"CODEX_HOME": str(home),
"TMPDIR": str(temporary),
"TEMP": str(temporary),
"TMP": str(temporary),
}
suffix = ".exe" if "windows" in package.target else ""
executor = package.cli_root / "bin" / f"codex-exec-server{suffix}"
for arguments in [
["--strict-config", "--listen", "stdio"],
["-c", "unknown_executor_option=true", "--strict-config", "--listen", "stdio"],
["--listen", "http://127.0.0.1:0"],
["--concurrent-requests", "0"],
[
"--aws-sigv4",
"--remote",
"https://example.openai.com",
"--environment-id",
"env-1",
],
["--remote-transport", "direct"],
["forward", "--connect", "ws://127.0.0.1:0"],
[
"-c",
"unknown_executor_option=true",
"forward",
"-c",
"analytics.enabled=false",
"--strict-config",
"--connect",
"ws://127.0.0.1:0",
"--remote",
"https://example.openai.com",
"--environment-id",
"env-1",
],
]:
outputs = []
for command in ([str(package.cli), "exec-server"], [str(executor)]):
result = subprocess.run(
[*command, *arguments],
cwd=tmp_path,
env=environment,
capture_output=True,
text=True,
timeout=45,
)
assert result.returncode != 0, arguments
outputs.append(
(
result.returncode,
result.stdout,
result.stderr.replace("codex exec-server", "codex-exec-server"),
)
)
assert outputs[0] == outputs[1], arguments
@pytest.mark.parametrize("entrypoint", ["codex", "codex-app-server"])
def test_app_server_runs_code_mode_through_python_sdk(
package: SmokePackage,
+1
View File
@@ -45,6 +45,7 @@ class PackageVariant:
class PackageInputs:
entrypoint_bin: Path
code_mode_host_bin: Path
exec_server_bin: Path
rg_bin: Path
bwrap_bin: Path | None
codex_command_runner_bin: Path | None
+7 -1
View File
@@ -21,6 +21,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
PACKAGE_VARIANTS["codex"],
build_entrypoint=False,
build_code_mode_host=False,
build_exec_server=False,
build_bwrap=False,
build_codex_command_runner=False,
build_codex_windows_sandbox_setup=False,
@@ -37,6 +38,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
PACKAGE_VARIANTS["codex"],
build_entrypoint=False,
build_code_mode_host=False,
build_exec_server=False,
build_bwrap=False,
build_codex_command_runner=False,
build_codex_windows_sandbox_setup=False,
@@ -53,6 +55,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
PACKAGE_VARIANTS["codex"],
build_entrypoint=False,
build_code_mode_host=False,
build_exec_server=False,
build_bwrap=False,
build_codex_command_runner=False,
build_codex_windows_sandbox_setup=False,
@@ -67,6 +70,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
PACKAGE_VARIANTS["codex"],
build_entrypoint=False,
build_code_mode_host=False,
build_exec_server=False,
build_bwrap=False,
build_codex_command_runner=True,
build_codex_windows_sandbox_setup=True,
@@ -81,11 +85,12 @@ class SourceBinariesForTargetTest(unittest.TestCase):
PACKAGE_VARIANTS["codex-app-server"],
build_entrypoint=False,
build_code_mode_host=True,
build_exec_server=True,
build_bwrap=False,
build_codex_command_runner=False,
build_codex_windows_sandbox_setup=False,
),
["codex-code-mode-host"],
["codex-code-mode-host", "codex-exec-server"],
)
def test_build_uses_prebuilt_windows_helpers_without_running_cargo(self) -> None:
@@ -103,6 +108,7 @@ class SourceBinariesForTargetTest(unittest.TestCase):
profile="release",
entrypoint_bin=entrypoint,
code_mode_host_bin=code_mode_host,
exec_server_bin=code_mode_host,
bwrap_bin=None,
codex_command_runner_bin=command_runner,
codex_windows_sandbox_setup_bin=sandbox_setup,
+36 -1
View File
@@ -2,6 +2,8 @@
from pathlib import Path
import hashlib
import io
import tarfile
import json
import shutil
import subprocess
@@ -12,6 +14,7 @@ import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from build_winget_package import prepare_winget_package
from codex_package.archive import write_archive, resolve_zstd_command
from codex_package.layout import build_package_dir
from codex_package.layout import validate_package_dir
from codex_package.targets import PACKAGE_VARIANTS
@@ -63,6 +66,7 @@ int main(void) {
flags = [
"--entrypoint-bin",
"--code-mode-host-bin",
"--exec-server-bin",
"--rg-bin",
]
if "linux" in target:
@@ -73,7 +77,7 @@ int main(void) {
# Preserved prebuilt inputs must not even need a strip tool.
command.extend(("--strip-tool", str(root / "absent-strip")))
subprocess.run(command, check=True, capture_output=True)
for name in ("codex", "codex-code-mode-host"):
for name in ("codex", "codex-code-mode-host", "codex-exec-server"):
executable = package / "bin" / name
self.assertEqual(
subprocess.check_output([str(executable)]),
@@ -97,6 +101,7 @@ int main(void) {
files = {
"bin/codex.exe": b"signed CLI",
"bin/codex-code-mode-host.exe": b"signed code mode host",
"bin/codex-exec-server.exe": b"signed executor",
"codex-resources/codex-command-runner.exe": b"signed runner",
"codex-resources/codex-windows-sandbox-setup.exe": b"signed setup",
"codex-resources/voice/bin/codex-voice-host.exe": b"signed voice host",
@@ -172,6 +177,7 @@ int main(void) {
code_mode_host_bin=touch_executable(
root / "codex-code-mode-host"
),
exec_server_bin=rg_bin,
rg_bin=rg_bin,
bwrap_bin=None,
codex_command_runner_bin=None,
@@ -180,13 +186,41 @@ int main(void) {
build_package_dir(package_dir, "1.2.3", variant, spec, inputs)
validate_package_dir(package_dir, variant, spec)
for suffix in (".tar.gz", ".tar.zst"):
archive_path = root / f"package{suffix}"
write_archive(package_dir, archive_path, force=False)
data = (
subprocess.check_output(
[
*resolve_zstd_command(),
"-d",
"--stdout",
str(archive_path),
]
)
if suffix == ".tar.zst"
else archive_path.read_bytes()
)
with tarfile.open(
fileobj=io.BytesIO(data), mode="r:*"
) as archive:
member = archive.getmember("bin/codex-exec-server")
self.assertTrue(member.mode & 0o111)
self.assertEqual(
archive.extractfile(member).read(),
rg_bin.read_bytes(),
)
self.assertEqual(
{
"rg": (package_dir / "codex-path" / "rg").read_bytes(),
"executor": (
package_dir / "bin/codex-exec-server"
).read_bytes(),
},
{
"rg": b"signed ripgrep binary",
"executor": b"signed ripgrep binary",
},
)
@@ -198,6 +232,7 @@ int main(void) {
inputs = PackageInputs(
entrypoint_bin=touch_executable(root / "codex-app-server"),
code_mode_host_bin=touch_executable(root / "codex-code-mode-host"),
exec_server_bin=touch_executable(root / "codex-exec-server"),
rg_bin=touch_executable(root / "rg"),
bwrap_bin=touch_executable(root / "bwrap"),
codex_command_runner_bin=None,
+10
View File
@@ -772,6 +772,16 @@ function Test-PackageContentsAreComplete {
"codex-resources\codex-command-runner.exe",
"codex-resources\codex-windows-sandbox-setup.exe"
)
try {
$metadataPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath((Join-Path $PackageDir "codex-package.json"))
$metadata = [System.IO.File]::ReadAllText($metadataPath)
# Older canonical packages do not advertise or contain the executor.
if ([regex]::IsMatch($metadata, '"execServer"\s*:')) {
$expectedFiles += "bin\codex-exec-server.exe"
}
} catch {
return $false
}
foreach ($name in $expectedFiles) {
if (-not (Test-Path -LiteralPath (Join-Path $PackageDir $name) -PathType Leaf)) {
return $false
+7
View File
@@ -1125,6 +1125,9 @@ install_package_release() {
if [ -f "$stage_release/codex-resources/bwrap" ]; then
chmod 0755 "$stage_release/codex-resources/bwrap"
fi
if [ -f "$stage_release/bin/codex-exec-server" ]; then
chmod 0755 "$stage_release/bin/codex-exec-server"
fi
ln -sf "bin/codex" "$stage_release/codex"
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
@@ -1178,6 +1181,10 @@ release_dir_is_complete() {
[ -x "$release_dir/codex" ] &&
[ -x "$release_dir/codex-path/rg" ] ||
return 1
# Older canonical packages do not advertise or contain the executor.
if grep -Eq '"execServer"[[:space:]]*:' "$release_dir/codex-package.json"; then
[ -x "$release_dir/bin/codex-exec-server" ] || return 1
fi
;;
legacy-platform-npm)
[ -x "$release_dir/codex" ] &&
+8 -4
View File
@@ -995,6 +995,7 @@ def run_installer_in(
root: Path,
release: str,
*,
installer_path: Path = INSTALL_SCRIPT,
metadata_failure: bool = False,
metadata_json: str | None = None,
releases_metadata_json: str | None = None,
@@ -1186,7 +1187,7 @@ def run_installer_in(
"TRUE" if use_mirror else "false"
)
result = subprocess.run(
["/bin/sh", str(INSTALL_SCRIPT)],
["/bin/sh", str(installer_path)],
capture_output=True,
check=False,
env=env,
@@ -1213,14 +1214,17 @@ def create_package_release(
metadata_version: str = VERSION,
binary_version: str = VERSION,
target: str = "aarch64-apple-darwin",
include_exec_server: bool = False,
) -> tuple[Path, Path, str]:
package_dir = root / "package"
(package_dir / "bin").mkdir(parents=True)
(package_dir / "codex-path").mkdir()
metadata = {"version": binary_version, "target": target, "entrypoint": "bin/codex"}
if include_exec_server:
metadata["execServer"] = "bin/codex-exec-server"
write_executable(package_dir / "bin/codex-exec-server", "#!/bin/sh\nexit 0\n")
(package_dir / "codex-package.json").write_text(
json.dumps(
{"version": binary_version, "target": target, "entrypoint": "bin/codex"}
),
json.dumps(metadata),
encoding="utf-8",
)
write_executable(