Codex Environment Setup Fails Before the Task Starts

Setup script bails before Codex can touch code — usually Node/Python version mismatch, private registry auth, or a slow install hitting the sandbox timeout.

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:

  1. In your registry, generate a read-only token (GitHub Personal Access Token with read:packages for GitHub Packages).
  2. In Codex settings → Secrets, add NPM_TOKEN=ghp_xxx.
  3. In setup.sh, materialize an .npmrc from 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; psycopg2psycopg2-binary) to skip native builds entirely.

Step 5: Speed up install to dodge timeouts

Move from npm installnpm 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.sh with set -euo pipefail so failures stop loudly instead of cascading
  • Store registry tokens as Codex secrets; write .npmrc from 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

Tags: #Codex #Troubleshooting #Environment #CI #Dependencies