Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 8 min read

Debugging a Failing GitLab Pipeline: A Systematic Approach

Random retries are not a debugging strategy. Here's the systematic way I diagnose failing GitLab CI jobs — from reading the trace to reproducing locally and using AI.

  • #gitlab
  • #cicd
  • #debugging
  • #troubleshooting
  • #pipelines
  • #ci

There’s a move I see constantly: a pipeline fails, someone clicks “Retry,” it passes, and everyone moves on. Sometimes that’s fine. Often it just hid a real problem that comes back at the worst possible time. After 25 years of staring at red pipelines, I’ve learned that debugging CI is a discipline, not a dice roll. Here’s the systematic approach I use.

First, classify the failure

Before reading a single log line, ask one question: is this failure about my code, or about the environment? Almost every CI failure is one of four types:

  • Code failure — a test legitimately failed, a lint rule fired. The pipeline is doing its job. Fix the code.
  • Environment failure — missing dependency, wrong image, bad runner. The pipeline config or infrastructure is wrong.
  • Flaky failure — passes on retry with no change. A real bug, usually a race or timing issue, just not where you think.
  • Config failure — the YAML itself is wrong: bad rules, missing artifact, broken needs.

Classifying first stops you from “fixing” your code when the problem was a stale Docker image.

Read the trace from the bottom up

The actual error is almost always near the end of the job log, but people scroll from the top and get lost. Jump to the bottom and read upward until you find the first real error — not the cascade of failures it caused. The exit code matters too: exit 1 from your script is different from exit 137 (out of memory, the OOM killer) or a runner_system_failure.

That OOM case (137) trips people up constantly. It looks like a code bug; it’s actually a memory limit. The fix is in the runner config, not the code.

Make the pipeline tell you more

When the trace isn’t enough, add temporary diagnostics. Dump the environment, list files, print versions:

debug-job:
  script:
    - echo "=== environment ==="
    - env | sort
    - echo "=== working dir ==="
    - pwd && ls -la
    - echo "=== tool versions ==="
    - node --version && npm --version
    - ./run-the-failing-thing.sh

Half the CI bugs I’ve chased come down to “the runner has a different version of something than my laptop.” Printing versions surfaces that in seconds.

Reproduce locally before you flail

Pushing a commit to test a fix is a slow, public feedback loop. Reproduce the job locally instead. Run the exact image with the same commands:

docker run --rm -it -v "$PWD:/app" -w /app node:20-slim bash
# now run the failing script step by step

This collapses a five-minute push-wait-fail cycle into a five-second local one. For pipeline logic bugs, glab ci lint and the CI lint endpoint validate your YAML before you ever push.

Use the right tool for each failure type

  • Config failure? CI lint and a careful read of your rules: and needs:.
  • Environment failure? Reproduce in the exact Docker image locally.
  • Flaky failure? Run the test in a loop locally (for i in {1..50}; do ...) to make it fail on demand. A flake you can reproduce is a flake you can fix.
  • Code failure? It’s just a normal bug — debug it like any other.

Hunt flakes, don’t tolerate them

Flaky tests are the most expensive failures because they erode trust in the whole pipeline. Once people start reflexively retrying, the pipeline stops being a signal. So I treat every flake as a real bug with a ticket. The usual culprits: shared state between tests, timing assumptions (sleep 1 and hope), test-order dependencies, and external services that aren’t stubbed. GitLab’s flaky-test detection can help you find them; fixing them is non-negotiable on any pipeline you want to trust.

Check what changed in the pipeline, not just the code

Sometimes the code is innocent and the pipeline changed — a template you include got a new version, a base image updated, a runner got upgraded. When a job that passed yesterday fails today with no code change, look at your pinned refs and image tags. An unpinned include or a :latest image is a time bomb that goes off on someone else’s schedule.

Where AI helps

This is one of the strongest uses of AI in CI/CD. Paste the failing job trace — scrubbed of secrets — and the relevant .gitlab-ci.yml and ask: “What’s the root cause of this failure, and is it a code, environment, config, or flaky issue?” A model reads a 2,000-line trace faster than you and won’t get tunnel vision on the first error. It’s especially good at decoding cryptic exit codes and spotting the version mismatch buried in the env dump.

Keep the human in the loop — verify the diagnosis before you act on it — but as a first-pass reader of logs, it’s excellent. I keep GitLab CI prompts for failure triage, and run the resulting pipeline fixes through our Code Review tool before merging.

The discipline that saves hours

  1. Classify the failure type before reading logs.
  2. Read the trace bottom-up to the first real error and check the exit code.
  3. Add diagnostics if the trace is thin.
  4. Reproduce locally instead of push-and-pray.
  5. Never tolerate flakes — ticket them as real bugs.
  6. Check pinned refs and images when something passed yesterday and fails today.

Random retries feel fast and cost you later. A systematic approach feels slower and saves you the 2am page when the flake you ignored finally takes down a deploy. Debug the pipeline like you’d debug production — because eventually, that’s what it’s protecting.

AI failure diagnoses are assistive, not authoritative. Always confirm the root cause yourself before shipping a fix, and never paste secrets into a model.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.