# Use @openai/codex dist-tags for platform binaries instead of separate package names (#11339)

https://github.com/openai/codex/pull/11318 introduced logic to publish
platform artifacts as separate npm packages (for example,
`@openai/codex-darwin-arm64`, `@openai/codex-linux-x64`, etc.). That
requires provisioning and maintaining multiple package entries in npm,
which we want to avoid.

We still need to keep the package-size mitigation (platform-specific
payloads), but we want that layout to live under a single npm package
namespace (`@openai/codex`) using dist-tags.

We also need to preserve pre-release workflows where users install
`@openai/codex@alpha` and get platform-appropriate binaries.

Additionally, we want GitHub Release assets to group Codex npm tarballs
together, so platform tarballs should follow the same `codex-npm-*`
filename prefix as the main Codex tarball.

## Release Strategy (New Scheme)

We publish **one npm package name for Codex binaries** (`@openai/codex`)
and use **dist-tags** to select platform-specific payloads. This avoids
creating separate platform package names while keeping the package size
split by platform.

### What gets published

#### Mainline release (`x.y.z`)

- `@openai/codex@latest` (meta package)
- `@openai/codex@darwin-arm64`
- `@openai/codex@darwin-x64`
- `@openai/codex@linux-arm64`
- `@openai/codex@linux-x64`
- `@openai/codex@win32-arm64`
- `@openai/codex@win32-x64`
- `@openai/codex-responses-api-proxy@latest`
- `@openai/codex-sdk@latest`

#### Alpha release (`x.y.z-alpha.N`)

- `@openai/codex@alpha` (meta package)
- `@openai/codex@alpha-darwin-arm64`
- `@openai/codex@alpha-darwin-x64`
- `@openai/codex@alpha-linux-arm64`
- `@openai/codex@alpha-linux-x64`
- `@openai/codex@alpha-win32-arm64`
- `@openai/codex@alpha-win32-x64`
- `@openai/codex-responses-api-proxy@alpha`
- `@openai/codex-sdk@alpha`

As an example, the `package.json` for `@openai/codex@alpha` (using
`0.99.0-alpha.17` as the `version`) would be:

```
{
  "name": "@openai/codex",
  "version": "0.99.0-alpha.17",
  "license": "Apache-2.0",
  "bin": {
    "codex": "bin/codex.js"
  },
  "type": "module",
  "engines": {
    "node": ">=16"
  },
  "files": [
    "bin"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/openai/codex.git",
    "directory": "codex-cli"
  },
  "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
  "optionalDependencies": {
    "@openai/codex-linux-x64": "npm:@openai/codex@0.99.0-alpha.17-linux-x64",
    "@openai/codex-linux-arm64": "npm:@openai/codex@0.99.0-alpha.17-linux-arm64",
    "@openai/codex-darwin-x64": "npm:@openai/codex@0.99.0-alpha.17-darwin-x64",
    "@openai/codex-darwin-arm64": "npm:@openai/codex@0.99.0-alpha.17-darwin-arm64",
    "@openai/codex-win32-x64": "npm:@openai/codex@0.99.0-alpha.17-win32-x64",
    "@openai/codex-win32-arm64": "npm:@openai/codex@0.99.0-alpha.17-win32-arm64"
  }
}
```

Note that the keys in `optionalDependencies` have "clean" names, but the
values have the tag embedded.

### Important note

**Note:** Because we never created the new platform package names on npm
(for example,
`@openai/codex-darwin-arm64`) since #11318 landed, there are no extra
npm packages to clean up.

## What changed

### 1. Stage platform tarballs as `@openai/codex` with platform-specific
versions

File: `codex-cli/scripts/build_npm_package.py`

- Added `CODEX_NPM_NAME = "@openai/codex"` and platform metadata
`npm_tag` values:
- `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`,
`win32-arm64`, `win32-x64`
- For platform package staging (`codex-<platform>` inputs), switched
generated `package.json` from:
  - `name = @openai/codex-<platform>`
  to:
  - `name = @openai/codex`
- Added `compute_platform_package_version(version, platform_tag)` so
platform tarballs have unique
versions (`<release-version>-<platform-tag>`), which is required because
npm forbids re-publishing
  the same `name@version`.

### 2. Point meta package optional dependencies at dist-tags on
`@openai/codex`

File: `codex-cli/scripts/build_npm_package.py`

- Updated `optionalDependencies` generation for the main `codex` package
to use npm alias syntax:
- key remains alias package name (for example,
`@openai/codex-darwin-arm64`) so runtime lookup behavior is unchanged
  - value now resolves to `@openai/codex` by dist-tag
- Stable releases emit tags like `npm:@openai/codex@darwin-arm64`.
- Alpha releases (`x.y.z-alpha.N`) emit tags like
`npm:@openai/codex@alpha-darwin-arm64`.

### 3. Publish with per-tarball dist-tags in release CI

File: `.github/workflows/rust-release.yml`

- Reworked npm publish logic to derive the publish tag per tarball
filename:
  - platform tarballs publish with `<platform>` tags for stable releases
- platform tarballs publish with `alpha-<platform>` tags for alpha
releases
- top-level tarballs (`codex`, `codex-responses-api-proxy`, `codex-sdk`)
continue using
the existing channel tag policy (`latest` implicit for stable, `alpha`
for alpha)
- Added fail-fast behavior for unexpected tarball names to avoid silent
mispublishes.

### 4. Normalize Codex platform tarball filenames for GitHub Release
grouping

Files: `scripts/stage_npm_packages.py`,
`.github/workflows/rust-release.yml`

- Renamed staged platform tarball filenames from:
  - `codex-linux-<arch>-npm-<version>.tgz`
  - `codex-darwin-<arch>-npm-<version>.tgz`
  - `codex-win32-<arch>-npm-<version>.tgz`
- To:
  - `codex-npm-linux-<arch>-<version>.tgz`
  - `codex-npm-darwin-<arch>-<version>.tgz`
  - `codex-npm-win32-<arch>-<version>.tgz`

This keeps all Codex npm artifacts grouped under a common `codex-npm-`
prefix in GitHub Releases.

### 5. Documentation update

File: `codex-cli/scripts/README.md`

- Updated staging docs to clarify that platform-native variants are
published as dist-tagged
  `@openai/codex` artifacts rather than separate npm package names.

## Resulting behavior

- Mainline release:
  - `@openai/codex@latest` resolves the meta package
- meta package optional dependencies resolve
`@openai/codex@<platform-tag>`
- Alpha release:
  - users can continue installing `@openai/codex@alpha`
- alpha meta package optional dependencies resolve
`@openai/codex@alpha-<platform-tag>`
- Release assets:
- Codex npm tarballs share `codex-npm-` prefix for cleaner grouping in
GitHub Releases

This preserves platform-specific payload distribution while avoiding
separate npm package names and
improves release-asset discoverability.

## Validation notes

- Verified staged `package.json` output for stable and alpha meta
packages includes expected alias targets.
- Verified staged platform package manifests are `name=@openai/codex`
with unique platform-suffixed versions.
- Verified publish tag derivation maps renamed platform tarballs to
expected stable and alpha dist-tags.
This commit is contained in:
Michael Bolin 2026-02-10 10:33:47 -08:00 committed by GitHub
parent 099ed802b2
commit d9c014efce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 65 additions and 12 deletions

View file

@ -595,9 +595,9 @@ jobs:
mkdir -p dist/npm
patterns=(
"codex-npm-${version}.tgz"
"codex-linux-*-npm-${version}.tgz"
"codex-darwin-*-npm-${version}.tgz"
"codex-win32-*-npm-${version}.tgz"
"codex-npm-linux-*-${version}.tgz"
"codex-npm-darwin-*-${version}.tgz"
"codex-npm-win32-*-${version}.tgz"
"codex-responses-api-proxy-npm-${version}.tgz"
"codex-sdk-npm-${version}.tgz"
)
@ -615,20 +615,44 @@ jobs:
NPM_TAG: ${{ needs.release.outputs.npm_tag }}
run: |
set -euo pipefail
tag_args=()
prefix=""
if [[ -n "${NPM_TAG}" ]]; then
tag_args+=(--tag "${NPM_TAG}")
prefix="${NPM_TAG}-"
fi
shopt -s nullglob
tarballs=(dist/npm/*-npm-"${VERSION}".tgz)
tarballs=(dist/npm/*-"${VERSION}".tgz)
if [[ ${#tarballs[@]} -eq 0 ]]; then
echo "No npm tarballs found in dist/npm for version ${VERSION}"
exit 1
fi
for tarball in "${tarballs[@]}"; do
npm publish "${GITHUB_WORKSPACE}/${tarball}" "${tag_args[@]}"
filename="$(basename "${tarball}")"
tag=""
case "${filename}" in
codex-npm-linux-*-"${VERSION}".tgz|codex-npm-darwin-*-"${VERSION}".tgz|codex-npm-win32-*-"${VERSION}".tgz)
platform="${filename#codex-npm-}"
platform="${platform%-${VERSION}.tgz}"
tag="${prefix}${platform}"
;;
codex-npm-"${VERSION}".tgz|codex-responses-api-proxy-npm-"${VERSION}".tgz|codex-sdk-npm-"${VERSION}".tgz)
tag="${NPM_TAG}"
;;
*)
echo "Unexpected npm tarball: ${filename}"
exit 1
;;
esac
publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}")
if [[ -n "${tag}" ]]; then
publish_cmd+=(--tag "${tag}")
fi
echo "+ ${publish_cmd[*]}"
"${publish_cmd[@]}"
done
update-branch:

View file

@ -15,7 +15,8 @@ This downloads the native artifacts once, hydrates `vendor/` for each package, a
tarballs to `dist/npm/`.
When `--package codex` is provided, the staging helper builds the lightweight
`@openai/codex` meta package plus all `@openai/codex-<platform>` native packages.
`@openai/codex` meta package plus all platform-native `@openai/codex` variants
that are later published under platform-specific dist-tags.
If you need to invoke `build_npm_package.py` directly, run
`codex-cli/scripts/install_native_deps.py` first and pass `--vendor-src` pointing to the

View file

@ -14,40 +14,49 @@ CODEX_CLI_ROOT = SCRIPT_DIR.parent
REPO_ROOT = CODEX_CLI_ROOT.parent
RESPONSES_API_PROXY_NPM_ROOT = REPO_ROOT / "codex-rs" / "responses-api-proxy" / "npm"
CODEX_SDK_ROOT = REPO_ROOT / "sdk" / "typescript"
CODEX_NPM_NAME = "@openai/codex"
# `npm_name` is the local optional-dependency alias consumed by `bin/codex.js`.
# The underlying package published to npm is always `@openai/codex`.
CODEX_PLATFORM_PACKAGES: dict[str, dict[str, str]] = {
"codex-linux-x64": {
"npm_name": "@openai/codex-linux-x64",
"npm_tag": "linux-x64",
"target_triple": "x86_64-unknown-linux-musl",
"os": "linux",
"cpu": "x64",
},
"codex-linux-arm64": {
"npm_name": "@openai/codex-linux-arm64",
"npm_tag": "linux-arm64",
"target_triple": "aarch64-unknown-linux-musl",
"os": "linux",
"cpu": "arm64",
},
"codex-darwin-x64": {
"npm_name": "@openai/codex-darwin-x64",
"npm_tag": "darwin-x64",
"target_triple": "x86_64-apple-darwin",
"os": "darwin",
"cpu": "x64",
},
"codex-darwin-arm64": {
"npm_name": "@openai/codex-darwin-arm64",
"npm_tag": "darwin-arm64",
"target_triple": "aarch64-apple-darwin",
"os": "darwin",
"cpu": "arm64",
},
"codex-win32-x64": {
"npm_name": "@openai/codex-win32-x64",
"npm_tag": "win32-x64",
"target_triple": "x86_64-pc-windows-msvc",
"os": "win32",
"cpu": "x64",
},
"codex-win32-arm64": {
"npm_name": "@openai/codex-win32-arm64",
"npm_tag": "win32-arm64",
"target_triple": "aarch64-pc-windows-msvc",
"os": "win32",
"cpu": "arm64",
@ -244,6 +253,8 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
package_json_path = CODEX_CLI_ROOT / "package.json"
elif package in CODEX_PLATFORM_PACKAGES:
platform_package = CODEX_PLATFORM_PACKAGES[package]
platform_npm_tag = platform_package["npm_tag"]
platform_version = compute_platform_package_version(version, platform_npm_tag)
readme_src = REPO_ROOT / "README.md"
if readme_src.exists():
@ -253,8 +264,8 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
codex_package_json = json.load(fh)
package_json = {
"name": platform_package["npm_name"],
"version": version,
"name": CODEX_NPM_NAME,
"version": platform_version,
"license": codex_package_json.get("license", "Apache-2.0"),
"os": [platform_package["os"]],
"cpu": [platform_package["cpu"]],
@ -294,7 +305,10 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
if package == "codex":
package_json["files"] = ["bin"]
package_json["optionalDependencies"] = {
CODEX_PLATFORM_PACKAGES[platform_package]["npm_name"]: version
CODEX_PLATFORM_PACKAGES[platform_package]["npm_name"]: (
f"npm:{CODEX_NPM_NAME}@"
f"{compute_platform_package_version(version, CODEX_PLATFORM_PACKAGES[platform_package]['npm_tag'])}"
)
for platform_package in PACKAGE_EXPANSIONS["codex"]
if platform_package != "codex"
}
@ -316,6 +330,12 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
out.write("\n")
def compute_platform_package_version(version: str, platform_tag: str) -> str:
# npm forbids republishing the same package name/version, so each
# platform-specific tarball needs a unique version string.
return f"{version}-{platform_tag}"
def run_command(cmd: list[str], cwd: Path | None = None) -> None:
print("+", " ".join(cmd))
subprocess.run(cmd, cwd=cwd, check=True)

View file

@ -26,6 +26,7 @@ _BUILD_MODULE = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(_BUILD_MODULE)
PACKAGE_NATIVE_COMPONENTS = getattr(_BUILD_MODULE, "PACKAGE_NATIVE_COMPONENTS", {})
PACKAGE_EXPANSIONS = getattr(_BUILD_MODULE, "PACKAGE_EXPANSIONS", {})
CODEX_PLATFORM_PACKAGES = getattr(_BUILD_MODULE, "CODEX_PLATFORM_PACKAGES", {})
def parse_args() -> argparse.Namespace:
@ -129,6 +130,13 @@ def run_command(cmd: list[str]) -> None:
subprocess.run(cmd, cwd=REPO_ROOT, check=True)
def tarball_name_for_package(package: str, version: str) -> str:
if package in CODEX_PLATFORM_PACKAGES:
platform = package.removeprefix("codex-")
return f"codex-npm-{platform}-{version}.tgz"
return f"{package}-npm-{version}.tgz"
def main() -> int:
args = parse_args()
@ -160,7 +168,7 @@ def main() -> int:
for package in packages:
staging_dir = Path(tempfile.mkdtemp(prefix=f"npm-stage-{package}-", dir=runner_temp))
pack_output = output_dir / f"{package}-npm-{args.release_version}.tgz"
pack_output = output_dir / tarball_name_for_package(package, args.release_version)
cmd = [
str(BUILD_SCRIPT),