Skip to main content
ZhimaYuandi
中文
← Back to blog
#codex#pnpm#windows#troubleshooting#tooling

Codex Installed but Won't Run: How pnpm Silently Skipped a 141MB Platform Package

A complete walkthrough of the `Missing optional dependency @openai/codex-win32-x64` error on Windows: pnpm silently ignores failed optional-dependency downloads, the network can't sustain transfers over 60MB, and how to rescue the install with curl resume + manual installation.

Coding Express 24 min

TL;DR

After installing the OpenAI Codex CLI on Windows (pnpm add -g @openai/codex), running it immediately throws:

Error: Missing optional dependency @openai/codex-win32-x64. Reinstall Codex: pnpm add -g @openai/codex@latest

Reinstalling once or twice as instructed still failed. The root cause turned out to be two pitfalls stacked on top of each other:

  1. pnpm silently ignores failed optionalDependencies downloads — the Windows platform binary (@openai/codex-win32-x64, 141MB) never downloaded, yet pnpm reported the install as successful. The failure only surfaced at runtime.
  2. The network can’t sustain long large-file downloads — the connection dropped at around 60–66MB, and pnpm restarts every retry from zero, so it never reaches 141MB.

The fix: use curl -C - (resume support) to pull the platform package completely, then manually install it into pnpm’s global virtual store. No need to reinstall Codex itself.

Symptoms: the command exists but throws as soon as it runs

$ codex
file:///C:/Users/peini/AppData/Local/pnpm/global/v11/.../node_modules/@openai/codex/bin/codex.js:107
  throw new Error(
        ^
Error: Missing optional dependency @openai/codex-win32-x64. Reinstall Codex: pnpm add -g @openai/codex@latest

The codex command itself exists (both the shim and the main package are installed), but the launcher script codex.js can’t find the platform-specific binary package at runtime and throws. The error message even helpfully suggests the reinstall command.

First wrong turn: reinstalling as instructed

Running pnpm add -g @openai/codex@latest, the install started downloading @openai/codex@0.153.4-win32-x64 (141.49 MB), then failed with operation timed out at around 60–66MB — and restarted from scratch, dropped again, restarted again… The official npm registry and the npmmirror mirror both behaved identically, which ruled out a registry problem: the local network just can’t hold a long-lived connection.

Key observations:

  • Every pnpm retry restarts the download at 0 bytes — no resume support;
  • The failed package was an optional dependency, so pnpm still finished with an “install succeeded” status and no failure notice;
  • Checking the virtual store at node_modules/.pnpm/: @openai+codex@0.153.4 exists, but the platform package directory simply doesn’t exist.

Checking the skipped list in node_modules/.modules.yaml revealed that win32-x64 wasn’t listed either — the metadata claims it was installed, but the filesystem says otherwise. Trust the filesystem, not the metadata.

Root cause: two pitfalls stacked

Pitfall 1: optional-dependency failures are silently ignored

Codex CLI ships like this: the main package @openai/codex (a pure-JS launcher) splits per-platform Rust binaries into separate optional dependency packages (@openai/codex-win32-x64, -darwin-arm64, -linux-x64…), resolved at runtime via process.platform + arch. This is a common pattern (esbuild and sharp work the same way).

The problem: when an optional dependency fails to download, pnpm doesn’t error by default. codex.js calls require.resolve("@openai/codex-win32-x64/package.json"), can’t find the package, and can only throw Missing optional dependency.

Pitfall 2: the network can’t sustain 60MB+ transfers, and pnpm retries from zero

Every source died at roughly the same size, which points to unstable sustained transfer on the local network (common with proxies, weak networks, or ISP throttling). pnpm’s fetch retry is “download the whole tarball again”, not “resume from the break point”, so as long as a single download exceeds what the network can sustain, the install can never complete.

The fix: curl resume + manual install

Step 1: pull the tarball with curl (key: -C - for resume)

curl -L -C - --retry 10 --retry-all-errors --retry-delay 3 \
  -o codex-win32-x64-0.153.4.tgz \
  "https://registry.npmmirror.com/@openai/codex/-/codex-0.153.4-win32-x64.tgz"

-C - makes curl continue from the byte position already downloaded after a dropped connection, so no matter how many times it breaks, the file eventually completes. The download broke several times in practice, but the file arrived complete: 141,495,386 bytes.

Step 2: extract and verify

tar -xzf codex-win32-x64-0.153.4.tgz
# Confirm the critical binary exists:
# package/vendor/x86_64-pc-windows-msvc/bin/codex.exe

Step 3: place it into the pnpm global virtual store (watch the naming rule)

The virtual store directory name follows @scope+name@version, where version is the full version field from that package’s package.json. The platform package’s version is 0.153.4-win32-x64, so the canonical directory name is:

node_modules/.pnpm/@openai+codex@0.153.4-win32-x64/node_modules/@openai/codex-win32-x64/

Note: it is not @openai+codex-win32-x64@0.153.4. I initially created the directory by guessing “package name + version” and require.resolve still failed.

The Codex launcher resolves dependencies upward from node_modules/.pnpm/@openai+codex@0.153.4/node_modules/, so you need to create a link to the platform package under its node_modules:

mklink /J "…\@openai+codex@0.153.4\node_modules\@openai\codex-win32-x64" "…\@openai+codex@0.153.4-win32-x64\node_modules\@openai\codex-win32-x64"

This is where I hit a PowerShell trap: links created with New-Item -ItemType Junction were unusable (they came out as symlinks with the target path rewritten to a wrong relative path). Switching to cmd /c mklink /J with absolute paths worked on the first try. For directory links on Windows, use mklink /J, not PowerShell’s New-Item.

Step 5: verify

# 1. Resolution check
node -e "const {createRequire}=require('node:module'); const r=createRequire('…/bin/codex.js'); console.log(r.resolve('@openai/codex-win32-x64/package.json'))"
# 2. Real run
codex --version   # → codex-cli 0.153.4 ✓

Wish list for pnpm

The two most painful points of this experience are both things pnpm could improve:

  1. Resumable downloads: send a Range header and resume retries from the last break point. Large packages (tens to hundreds of MB) would stop being uninstallable on weak networks. A single curl -C - solves this; a package manager shouldn’t be worse than that.
  2. Explicit warnings for failed optional dependencies: failing the install can be fine (they’re optional), but at minimum the install should end with a clear notice like “the following optional dependencies failed to download: @openai/codex-win32-x64 (141.49MB)” instead of making people hunt it down after a runtime error. npm install has the same problem.
  3. A side note on metadata consistency: the skipped list in .modules.yaml disagreed with what actually landed on disk, which misled the investigation for a while. It would help if pnpm verified that “declared installed items” and “actual files” match after install.

Until pnpm supports this, here’s what we can do ourselves:

  • Right after installing any package that bundles native binaries, run --version immediately to verify — don’t wait until you need it to find out;
  • Pre-warm large packages: if a download of tens of MB or more fails, curl -C - the tarball first, then feed it to the package manager or install it manually;
  • Switch network channels: retry after changing proxy / mirror / network environment — it often passes on the first try.

Pitfall checklist

Pitfall Symptom Avoidance
Optional dependency fails silently Install “succeeds”, runtime throws Missing dependency Run --version right after installing
Unstable network on long connections Large download drops around 60MB curl -C - resume to pull the tarball
pnpm retries from zero Every retry resets progress; never completes Switch channels / pre-warm the tarball
Wrong virtual store directory name require.resolve still fails Name it @scope+name@version using the package.json version field
PowerShell New-Item links unusable Produces broken symlinks, mangled target Use cmd /c mklink /J with absolute paths
Metadata vs. actual files disagree .modules.yaml claims installed; file missing Trust the filesystem, not the metadata