You queue a Codex cloud task and it never gets past Setting up environment. The setup log shows npm error code E401, or python: command not found, or it just hangs and ends without ever planning a change. Codex never reads your code: the container couldn’t even finish your setup script, so the agent phase never started.
Fastest fix (works for most repos): in Codex settings → Environments, open your environment, click Set package versions, and pin Node / Python to what your lockfile expects. That resolves the single most common cause (runtime mismatch) without touching your setup script. If the log shows 401/E401 instead, it’s a private-registry token (jump to fix 3). If it ends in a timeout with no package error, it’s the install budget (fix 5).
Codex cloud runs your repo in a container built from the openai/codex-universal base image, then runs your setup script (with internet access) before handing control to the agent. As of June 2026, six causes account for nearly every setup failure: wrong runtime version, missing private-registry token, slow install hitting the cached-container budget, missing native build deps, missing system packages, or a setup script that fails silently halfway through. Below: how to tell which bucket you’re in, then the fixes.
Which bucket are you in?
Match the first real error in the setup log to a cause. Scroll past the first few lines — the root cause is often buried, not at the top.
| Log signal | Likely cause | Fix |
|---|---|---|
EBADENGINE, Unsupported engine, requires: node@…, Python SyntaxError: invalid syntax | Runtime version mismatch | Fix 1 |
401, 403, E401, Unauthorized, Not Found against your registry, package starts with @your-org/ | Private registry needs a token | Fix 3 |
Ends in timed out / timeout with no package error | Install too slow for the container budget | Fix 5 |
gcc: command not found, Python.h: No such file, pg_config: command not found, node-gyp stack trace | Missing native build deps | Fix 4 |
| First error isn’t the real one; build fails for a confusing reason | Script continued after a silent failure | Fix 2 |
getaddrinfo ENOTFOUND, ECONNREFUSED, 503 from a non-public host | Network egress blocked | Fix 6 |
Common causes, in order of hit rate
1. Project runtime doesn’t match the base image
The codex-universal image defaults to Node 22 (with 18 and 20 also installed) and Python 3.12 as of June 2026 — not the older Node 20 / Python 3.11 some guides still cite. If your lockfile was generated under Node 18, or your code uses a Python feature your pinned version doesn’t have, npm ci throws an engine error or imports crash.
How to spot it: look for EBADENGINE, Unsupported engine, requires: node@<version>, or a Python SyntaxError. Add node -v && python --version as the first lines of your setup script — a mismatch is then obvious.
2. Private npm / pip registry needs a token
Your package.json pulls @your-org/private-pkg from GitHub Packages or a self-hosted Verdaccio. The container has no auth, so npm ci returns 401 Unauthorized (newer npm prints npm error code E401).
How to spot it: search the log for 401, 403, Unauthorized, or Not Found next to your registry URL. If the failing package starts with @your-org/, this is it.
3. Install exceeds the cached-container budget
Codex caches container state for up to 12 hours to speed up follow-up tasks, but a cold first run still has to finish setup in one shot. A fresh pnpm install on a large monorepo, or pulling a 500 MB Playwright browser bundle, can run long enough to fail setup.
How to spot it: the log ends with timed out and no specific package error. Time the same install locally — if a cold install takes several minutes, that’s your problem.
4. Native build dependencies missing
node-gyp, sharp, node-canvas, bcrypt, lxml, pillow, and psycopg2 need gcc, python3-dev, libpq-dev, or similar. codex-universal is broad but won’t have every -dev header your packages compile against.
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 long; read past the first 50 lines to the real cause.
5. Setup script silently continues after a failure
Your setup script runs npm install && npm run build. The install half-succeeds (warnings, not a non-zero exit), the script keeps going, and the build fails for a confusing reason. The real cause was two commands earlier.
How to spot it: the first error in the log isn’t the actual root cause. Add set -euo pipefail at the top and re-run — the true failure surfaces and stops the script there.
6. Network egress blocked or rate-limited
The setup phase has internet access, but a self-hosted registry behind a corporate VPN may be unreachable from the container, and high-volume raw downloads can be rate-limited. (Note: the agent phase has internet off by default; that affects code that fetches at runtime, not your setup install.)
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 there, container egress is the issue.
Shortest path to fix
Ordered by ROI. Fixes 1–3 clear the version + token + native-deps trifecta.
Fix 1: Pin runtime versions in the environment, not just a dotfile
The modern, cache-friendly way is to pin in the environment itself. The image resolves CODEX_ENV_* variables at container start via its entrypoint — faster and more reliable than reinstalling a runtime inside your script.
- UI: Codex settings → Environments → your environment → Set package versions → choose Node / Python (and others).
- Or env vars in the same environment settings:
CODEX_ENV_NODE_VERSION=20
CODEX_ENV_PYTHON_VERSION=3.11
Supported values as of June 2026: Node 18, 20, 22; Python 3.10, 3.11, 3.12, 3.13, 3.14. (There are equivalents for Go, Rust, Ruby, Java, Swift, and PHP — e.g. CODEX_ENV_GO_VERSION.) Dotfiles like .nvmrc / .python-version still work as a fallback, but the env-var route survives cache resumes better.
Fix 2: Make the setup script fail fast and log context
Put this in your setup script (configured in the environment, or your repo’s setup file):
#!/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 (only if a build script exists) ==="
npm run build --if-present
echo "=== Setup done ==="
set -euo pipefail is the load-bearing line: it stops at the first error instead of chaining failures into a confusing one downstream.
One gotcha that bites people: the setup script runs in a separate Bash session from the agent, so export FOO=bar in setup does not carry into the agent phase. If the agent needs an env var at runtime, write it to ~/.bashrc (or set it as an environment variable in the environment settings), not with a bare export in setup.
Fix 3: Materialize the registry token from a Codex secret
Secrets are encrypted and, by design, available only during setup — they are removed before the agent phase starts. So you must write the .npmrc during setup; you can’t lazily auth later.
- Generate a read-only token (for GitHub Packages: a Personal Access Token with
read:packages). - In your Codex environment settings, add it as a Secret, e.g.
NPM_TOKEN. - In the setup script, write
~/.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 real .npmrc / pip.conf — only generate it from the secret at setup time. Note: editing a secret invalidates the cached container, so the next task rebuilds with the new value.
Fix 4: Install native build dependencies up front
If package.json includes sharp, canvas, bcrypt, or any node-gyp consumer, install the toolchain first (the base image is Debian-based):
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 packages with psycopg2, lxml, or pillow, add libpq-dev, libxml2-dev, libxslt1-dev, and libjpeg-dev.
Better yet, skip the compile entirely with prebuilt wheels/binaries: psycopg2 → psycopg2-binary, and let sharp pull its prebuilt libvips instead of building from source.
Fix 5: Speed up the install to stay inside the budget
Switch npm install → npm ci (honors the lockfile, skips resolution, faster and deterministic). For pnpm / yarn:
# pnpm
pnpm install --frozen-lockfile --prefer-offline
# yarn (berry)
yarn install --immutable
For monorepos, install only what the task touches:
# pnpm workspace — only the API package
pnpm --filter @your-org/api install
Because Codex caches the container for up to 12 hours, push slow, rarely-changing steps into the setup script (so they get cached) and use the optional maintenance script to refresh only what drifts when a cached container resumes. That keeps repeat tasks fast.
Fix 6: Validate setup locally before pushing the task
Don’t debug through Codex’s setup-only logs. Reproduce in the actual base image:
docker pull ghcr.io/openai/codex-universal:latest
docker run --rm -it -v "$(pwd)":/repo -w /repo \
ghcr.io/openai/codex-universal:latest bash
# inside the container:
bash ./setup.sh
If it succeeds there (with the same secrets wired), the cloud container will too. If it fails locally, fix it with full tooling instead of guessing from truncated logs.
How to confirm it’s fixed
- Trigger a fresh task and watch the setup log reach
=== Setup done ===(or your final echo) before the agent starts planning. - Confirm the agent phase actually edits code — a clean setup but a stalled agent is a different problem.
- If you changed a runtime version, secret, or the setup/maintenance script, the cache was invalidated automatically; if behavior looks stale, hit Reset cache on the environment page and re-run once to rebuild cleanly.
Prevention
- Pin runtimes in the environment (Set package versions /
CODEX_ENV_*), not by hoping the base image guesses right - Start every setup script with
set -euo pipefailso failures stop loudly instead of cascading - Store registry tokens as Secrets and write
.npmrcfrom them in setup — secrets are gone by the agent phase, so do it then - Need an env var in the agent phase? Write it to
~/.bashrc; a bareexportin setup won’t persist - Prefer prebuilt binaries (
psycopg2-binary, prebuiltsharp) over source compiles - Use
npm ci/--frozen-lockfile— faster, deterministic, lockfile-respecting - Keep cold setup short and let the 12-hour cache plus a small maintenance script carry repeat tasks
FAQ
Where exactly do I configure the setup script and secrets? In Codex settings → Environments. Open the environment for your repo; the setup script, optional maintenance script, environment variables, and secrets all live there.
Why does my export in setup not work in the agent phase?
Setup and the agent run in separate Bash sessions. Variables exported during setup don’t carry over. Put them in ~/.bashrc or add them as environment variables in the environment settings instead.
My private package still 401s even though I added the secret. Why?
Secrets are only injected during setup, so the .npmrc (or pip auth) has to be written by the setup script itself. If you rely on auth during the agent phase, it fails because secrets are removed before the agent starts. Also confirm the token scope (read:packages for GitHub Packages) and that editing the secret rebuilt the cached container.
Codex keeps re-running the full setup every task — can I cache it? Container state is cached for up to 12 hours, but changing the setup or maintenance script, an environment variable, or a secret invalidates the cache and forces a full rebuild. Keep those stable, and use the maintenance script for the parts that legitimately need refreshing.
Which Node and Python versions does the cloud image ship by default?
As of June 2026, codex-universal defaults to Node 22 (18 and 20 also available) and Python 3.12, with Go, Rust, Ruby, Java, Swift, and PHP toolchains present. Pin any of them with CODEX_ENV_* or the Set package versions UI.
Can my setup script reach the internet? Yes — the setup phase has internet access to install dependencies. The agent phase has internet off by default, so don’t rely on runtime network fetches unless you enable internet access for that environment.
Related
- Codex cannot access private repo
- Codex fails to run build
- Codex doesn’t update the lockfile
- Codex PR too large to merge
Tags: #Codex #Troubleshooting #Environment #CI #Dependencies