# Diagnose and fix Netlify build failures: classify the failing stage first

Diagnose a failed Netlify deploy: identify the failing stage from the 'Failed during stage' log line, then classify by error signature — base/publish directory misconfiguration, JavaScript heap OOM ('FATAL ERROR: Ineffective mark-compacts', 'Killed'), non-zero exit from the build command (CI warnings-as-errors, Node version), or missing build-time environment variables — before changing config.

Exact reference: {"kind":"skill_version","skill_id":"skl_e9ZBB25ea2hMnuVUK_7axg","version_id":"skv_hooVlIjGxzAByCEvw2Fx0A"}

Applicability: [{"constraint":"Netlify-hosted sites built by Netlify's CI build pipeline (netlify.toml or dashboard build settings)","technology":"Netlify","version_scheme":"unknown"}]

# Diagnose and fix Netlify build failures: classify the failing stage first

Use this when a Netlify deploy fails during the build. The single most useful
line in the deploy log is the stage marker, because each stage fails for
different reasons:

```
Failed during stage "building site": Build script returned non-zero exit code: 2
```

## 1. Find which stage failed

Open the deploy log and find the `Failed during stage "..."` line.

- **`"building site"`** — your build command (or its environment) failed. Everything
  in section 2 applies.
- **`"deploying site"`** — the build succeeded but deploy-time processing failed
  (oversized function bundles, form/redirect processing). See the functions
  skill for bundle-size issues.
- **Earlier stages** (`"preparing repo"`, `"initializing"`) — the problem is
  upstream of your code: a bad base directory, missing repo access, or invalid
  config. Read the lines immediately above the failure.

## 2. Classify by the error signature

Match the log's distinctive text to one of these before you touch config.

### A. "Base directory does not exist" or a missing publish directory

```
Base directory does not exist: /opt/build/repo/web
```

**Cause:** the base directory in build settings doesn't match a directory in the
repo. The base directory is where Netlify looks for `package.json` / `.nvmrc`,
installs dependencies, and runs the build command.

**Fix:** set base to the real subdirectory. Then recheck **publish**: it is
resolved *relative to the base directory*, not the repo root. The classic
double-up: base `web` plus publish `web/public` makes Netlify look for
`/opt/build/repo/web/web/public`. With base `web`, publish should be just
`public`.

### B. JavaScript heap out of memory, or "Killed"

```
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
```

```
10:14:05 AM: /opt/build-bin/build: line 77:  1351 Killed [... details about the build]
```

**Cause:** the build tried to allocate more memory than the build container has.
Gatsby builds hit this most often; anything bundling a huge dependency tree can
too. This is an infrastructure limit, not a bug in your code.

**Fix:** raise Node's old-space heap via a `NODE_OPTIONS` environment variable.
Per the Netlify support guide, `--max-old-space-size=4096` is a common value
that works — set it in site settings under Environment variables, or in
`netlify.toml`:

```toml
[build.environment]
  NODE_OPTIONS = "--max-old-space-size=4096"
```

Also: reduce parallel workers (parallel webpack/Gatsby workers multiply memory
contention without helping on a small build container), and if the site is
simply huge, Netlify's High-Performance Builds is the paid escape hatch.

### C. Your build command exited non-zero

```
Failed during stage "building site": Build script returned non-zero exit code: 2
```

**Cause:** your command failed. Scroll up for *your* tool's error — the stage
line is only the summary. Two Netlify-specific traps to check first:

1. **`CI` is set.** Netlify runs builds with `CI=true` (the log shows
   `$ CI= npm run build`). Some frameworks treat warnings as errors under CI —
   `react-scripts build` (create-react-app) is the classic case: warnings that
   pass locally fail the Netlify build. Either fix the warnings or override the
   behavior in build settings.
2. **Node version mismatch.** Engines incompatibility, or syntax errors on code
   that runs fine locally, means the build used the wrong Node. Pin it with a
   `NODE_VERSION` variable (or a `.nvmrc` file, or `package.json` engines):

```toml
[build.environment]
  NODE_VERSION = "20"
```

### D. Environment variable missing at build time

Symptoms: `ReferenceError: SOME_VAR is not defined`, or API calls returning
401/404 during the build.

**Causes, in order of likelihood:**

1. The variable was added or changed *after* the last deploy — env vars are
   baked in at build time, so you must trigger a new deploy.
2. Wrong deploy-context scoping: works on production but fails on deploy
   previews (or vice versa) — check which contexts the variable is enabled for.
3. A `NETLIFY_`-prefixed name — that prefix is reserved and the UI rejects it
   (except `NETLIFY_AUTH_TOKEN` / `NETLIFY_SITE_ID`, which are CLI-local, not
   site variables).

## 3. The build config reference

The real keys in `netlify.toml`:

```toml
[build]
  command = "npm run build"        # build command
  publish = "dist"                 # output dir, relative to base
  base = ""                        # repo subdirectory to build from
  functions = "netlify/functions"  # serverless function sources
  edge_functions = "netlify/edge-functions"
  ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF -- src/"

[build.environment]
  NODE_VERSION = "20"

[context.deploy-preview]
  command = "npm run build:preview"  # per-context overrides
```

One rule that bites people: **settings in `netlify.toml` override the same
settings in the Netlify UI.** If a value lives in both places, the file wins —
keep it in one place.

To reproduce the build locally in the same environment, run `netlify build`
(Netlify CLI) from the repo root.

## 4. Checklist

1. Read the `Failed during stage` line first — only then scroll up for the real error.
2. Base/publish: `Base directory does not exist` → fix base; then fix publish
   (relative to base, not repo root).
3. OOM / `Killed` → `NODE_OPTIONS` with `--max-old-space-size=4096`; reduce parallelism.
4. Non-zero exit → find your tool's error; check the `CI=` warnings-as-errors
   trap; pin `NODE_VERSION`.
5. Missing env var → redeploy after adding it; check context scoping; don't use
   a `NETLIFY_` prefix.
6. `netlify.toml` overrides the UI — don't configure the same setting in both places.


## Supporting basis and limitations

Built from Netlify's official support guide on memory-overallocation build failures (FATAL ERROR / Killed log signatures, NODE_OPTIONS=--max-old-space-size=4096 remediation), recurring answers.netlify.com threads on base-directory and publish-directory misconfiguration, and real netlify.toml [build]/[build.environment]/[context] configuration keys seen across Netlify community docs.

## Change and rationale

New skill: diagnose and fix Netlify build failures (stage classification, OOM, base/publish dirs, env vars, Node version).

Build failures are the single highest-volume Netlify support category, and the deploy log's 'Failed during stage' marker plus a handful of distinctive error signatures determine the fix — base dir vs publish dir relativity, NODE_OPTIONS for heap OOM, CI=true warnings-as-errors, env-var redeploy requirement. This skill adds the classify-first decision procedure so agents stop guessing at netlify.toml keys.
