You queue a Codex task and it never makes it past “Setting up environment.” Logs show something like npm ERR! 401 Unauthorized or python: command not found or just a 10-minute hang ending in Setup timed out. Codex never reads your code, never makes a plan — the sandbox couldn’t even reach pnpm install.
The sandbox runs a minimal base image and your setup.sh (or .codex/setup.sh). Most setup failures are one of six causes: wrong runtime version, missing private-registry token, slow install, native build deps, missing system packages, or a setup script that silently fails halfway. Below: how to spot which, plus the fixed setup template that handles 90% of repos.
Common causes
Ordered by hit rate, highest first.
1. Project’s Node/Python version doesn’t match Codex base image
Codex sandbox typically ships Node 20 and Python 3.11. Your project expects Node 18 (lockfile generated under it) or Python 3.12 (using match syntax). npm install fails on engine mismatch, or import statements crash.
How to spot it: Look for EBADENGINE, Unsupported engine, SyntaxError: invalid syntax (Python), or requires: node@<version>. Run node -v && python --version first thing in your setup script — if the version mismatches, that’s the cause.
2. Private npm / pip registry needs a token
Your package.json pulls from @your-org/private-pkg on a private GitHub Packages or Verdaccio. Codex has no .npmrc token, so npm install returns 401 Unauthorized.
How to spot it: Search the setup log for 401, 403, Unauthorized, Not Found against your registry URL. If the failing package starts with @your-org/, this is it.
3. Install exceeds the sandbox timeout
Codex sandboxes have a setup timeout (typically 5–10 minutes). A fresh pnpm install on a large monorepo, or pulling a 500MB Docker base for Playwright, can blow through it.
How to spot it: Setup log ends with timeout and no specific package error. Time the same install locally — if it takes >5 min cold, the sandbox is hitting the cap.
4. Native build dependencies missing
node-gyp, sharp, node-canvas, lxml, pillow, psycopg2-binary need gcc, python-dev, libpq-dev or similar system packages. The Codex base image is minimal — these aren’t preinstalled.
How to spot it: Search for gcc: command not found, Python.h: No such file, pg_config: command not found, or a node-gyp stack trace. Native compile failures are loud once you read past the first 50 lines.
5. Setup script silently continues after a failure
Your setup.sh runs npm install && npm run build. The install partially succeeds (warnings, not errors), the script continues, the build fails for a confusing reason. The actual cause is two commands earlier.
How to spot it: First error in the log isn’t the actual root cause. Re-run with set -euo pipefail at the top of the script — the real error surfaces.
6. Network egress blocked or rate-limited
Codex sandboxes restrict outbound traffic. Some registries (e.g., a self-hosted one on a corporate VPN) are unreachable. GitHub raw downloads may be rate-limited.
How to spot it: getaddrinfo ENOTFOUND, connect ECONNREFUSED, or 503 Service Unavailable from a non-public hostname. Try the same URL from another network — if it works, sandbox egress is the issue.
Shortest path to fix
Ordered by ROI. Steps 1–3 fix the version + token + native-deps trifecta.
Step 1: Pin runtime versions Codex can detect
Add to repo root:
# .nvmrc
20.11.0
# .python-version
3.11.7
Codex sandbox autodetects these and uses nvm use / pyenv install before npm install. If your project actually needs a different version, pin it and Codex will swap in.
For Bun, Deno, or Java, add the equivalent (.bun-version, deno.json, .java-version).
Step 2: Add a setup.sh that fails fast and logs context
Put this in .codex/setup.sh (or wherever your Codex config expects):
#!/usr/bin/env bash
set -euo pipefail # bail on first error, on unset var, on pipe failure
echo "=== Runtime versions ==="
node -v
python --version || echo "python not present"
echo "=== System deps ==="
which gcc make pkg-config || true
echo "=== Install ==="
# Prefer ci over install — deterministic and faster
npm ci --no-audit --no-fund
echo "=== Build ==="
npm run build --if-present
echo "=== Setup done ==="
set -euo pipefail is the critical line — it stops the script at the first error instead of silently chaining failures.
Step 3: Set registry tokens as Codex secrets
For private npm packages:
- In your registry, generate a read-only token (GitHub Personal Access Token with
read:packagesfor GitHub Packages). - In Codex settings → Secrets, add
NPM_TOKEN=ghp_xxx. - In setup.sh, materialize an
.npmrcfrom the secret:
cat > ~/.npmrc <<EOF
@your-org:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
always-auth=true
EOF
For pip:
export PIP_INDEX_URL="https://${PYPI_USER}:${PYPI_TOKEN}@pypi.your-org.com/simple/"
pip install -r requirements.txt
Never commit the .npmrc/.pip.conf itself — only the template-from-env approach.
Step 4: Install native build dependencies up front
If your package.json includes sharp, canvas, bcrypt, or a node-gyp consumer, install build tools first:
# Ubuntu/Debian base (most Codex sandboxes)
apt-get update -qq
apt-get install -y --no-install-recommends \
build-essential \
python3-dev \
libpq-dev \
libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev \
libvips-dev # for sharp
For Python projects with psycopg2, lxml, pillow: add libpq-dev, libxml2-dev, libjpeg-dev to the list.
If you can, switch to prebuilt binaries (sharp → --platform=linux --arch=x64; psycopg2 → psycopg2-binary) to skip native builds entirely.
Step 5: Speed up install to dodge timeouts
Move from npm install → npm ci (skips dependency resolution, faster + deterministic). For pnpm/yarn:
# pnpm
pnpm install --frozen-lockfile --prefer-offline
# yarn
yarn install --frozen-lockfile --offline-mirror=.npm-offline
For monorepos, install only what the task touches:
# pnpm workspace, only build the API package
pnpm --filter=@your-org/api install
Cache node_modules between Codex runs if your harness supports it — first run is slow, subsequent are seconds.
Step 6: Validate setup locally before pushing the task
Don’t debug environment problems through Codex logs. Reproduce locally:
docker run --rm -it -v $(pwd):/repo -w /repo node:20-bookworm bash
# inside:
bash .codex/setup.sh
If it succeeds in the container, the sandbox will too (assuming secrets are wired). If it fails locally, fix it there with full tooling instead of through Codex’s setup-only logs.
Prevention
- Always pin runtime versions in
.nvmrc/.python-version— never assume the sandbox guesses right - Start every
setup.shwithset -euo pipefailso failures stop loudly instead of cascading - Store registry tokens as Codex secrets; write
.npmrcfrom env in setup.sh, never commit it - For native dependencies, prefer prebuilt binaries (
sharp,psycopg2-binary) over source builds - Use
npm ci/pnpm install --frozen-lockfile— faster, deterministic, lockfile-respecting - Validate the setup script in a clean Docker container before pushing tasks to Codex
- Keep setup time under 3 minutes so you have headroom inside the sandbox timeout
Related
Tags: #Codex #Troubleshooting #Environment #CI #Dependencies