A red build usually ends with one useless line: Process completed with exit code 1. The real cause is somewhere above it, often thousands of lines up, and the loudest error is rarely the first one.

Rule 1: the first real error beats the last line

Errors cascade. A package that fails to install makes the build fail, which makes the container step fail, which makes the job fail. If you read from the bottom you fix the symptom.

Read in this order:

  1. Find the step that went red (the step header just above the first error).
  2. Inside it, find the first line that names a problem, not the one that says "failed".
  3. Ask which kind of problem it is. The kind decides what to check next.

Rule 2: sort the cause into a kind

Kind What it looks like Check next
Out of memory heap out of memory, exit code 137 runner memory, parallel jobs
Dependency ERESOLVE, Cannot find module, No matching distribution lockfile vs manifest, registry reachable
Network ECONNRESET, Could not resolve host, 502 status page of that host, one re-run
Timeout timed out, run cancelled duration of earlier green runs
Test failure AssertionError, FAILED tests/... run that test alone, same environment
Permission 403 Forbidden, Permission denied token scope, secret exists in this environment

A worked example (invented log)

#9 [build 4/6] RUN npm run build #9 45.11 FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory ... ERROR: failed to solve: process "/bin/sh -c npm run build" did not complete successfully: exit code: 134 ##[error]Process completed with exit code 1. 

Read from the bottom, you would blame the container build. Read from the first problem, the build step simply ran out of memory. The container error is a side effect. The fix is memory (or less parallelism), not the Dockerfile.

A second example, a test that failed because the network failed:

FAIL src/api.test.js FetchError: request to api.example.test/v1/profile failed, reason: read ECONNRESET Tests: 1 failed, 4 passed, 5 total 

Here the test is not wrong. Re-run once before touching code. If it fails again in the same place, then look at the host.

Do not paste secrets into anything

Logs often contain tokens, bearer headers and passwords inside URLs. Mask them before pasting a log anywhere, including an AI chat.

If you want this automated

I wrote a small local tool for it (Python, standard library, reads one log file, masks credentials, changes nothing). It is called ci-failure-triage and it is a paid skill on Agensi ($5). The method above works without it.

Disclosure: I made the tool mentioned in the last section. This post was drafted by an AI assistant (Claude) and reviewed by me before publishing.