Ship the standalone executor in release packages (#57837)

## What changed

- Include `codex-exec-server` in canonical CLI and app-server packages and advertise its path through `execServer` in `codex-package.json`.
- Build and sign the executor in release workflows, preserve signed prebuilt inputs, and verify packaged signatures. Use empty entitlements for the macOS executor.
- Have installers check for the executor when package metadata advertises it, while continuing to accept older packages.
- Omit the standalone executor and its manifest entry from npm packages to stay within npm's package size limit. Keep `codex exec-server` and its runtime helpers available.

## Testing

Add package smoke tests for executor initialization, version reporting, filesystem and process sandbox enforcement, and invalid-argument/configuration parity with `codex exec-server`. Extend archive, npm staging, and macOS signing tests to cover executor contents, executable permissions, preserved bytes, and entitlement verification.

GitOrigin-RevId: bc741fe7faa6fe631d076de18d448c55247c23c7
This commit is contained in:
Dmitriy Kovalenko
2026-10-28 21:09:05 +00:00
committed by copyberry
parent d359945843
commit b91f2515f5
26 changed files with 625 additions and 55 deletions
+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(