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:
- 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. - 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.4exists, 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.
Step 4: mklink /J to create the link (avoid the PowerShell trap)
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:
- Resumable downloads: send a
Rangeheader and resume retries from the last break point. Large packages (tens to hundreds of MB) would stop being uninstallable on weak networks. A singlecurl -C -solves this; a package manager shouldn’t be worse than that. - 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 installhas the same problem. - A side note on metadata consistency: the
skippedlist in.modules.yamldisagreed 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
--versionimmediately 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 |