Guide
Flaky or real? Diagnose a red CI build
Goal
Your CI build went red. Before you change any code, find out whether the failure is a real regression, a flaky test, or broken infrastructure β with commands you can copy, expected output, and a decision at the end.
Why this matters
A red build is not the same as a real bug. Google's testing team reported in 2016 that almost 16% of their tests showed some level of flakiness, that about 1.5% of all test runs reported a flaky result, and β the number that should change how you triage β about 84% of pass-to-fail transitions involved a flaky test, not a real regression. Microsoft has attributed roughly 25% of CI failures to flaky tests, with other studies putting the figure as high as 50β56% before remediation (Amikoo, October 2026). Research published in the IEEE Transactions on Software Engineering in 2026 found undetected flaky failures made up 9.8%β16.3% of failed CI pipeline runs in the projects studied.
The real danger is not the noise β it is what the noise teaches your team. Google's engineers wrote that it is "quite common to ignore legitimate failures in flaky tests due to the high number of false-positives," and that it is human nature to ignore alarms with a history of false signals. Once developers learn to re-run a red build without investigating, real regressions start looking exactly like noise.
Flaky, real, or infra?
Use this decision table on the failing build's log before touching code:
- Real regression β the test fails consistently on a commit where it used to pass, and nothing else in the run looks off. The failing test's name, error, and line are the same on every re-run.
- Flaky β the test passes and fails on the same commit with no code change in between, or flips frequently across recent runs. Google's definition is exactly this: a test that exhibits both a passing and a failing result with the same code.
- Infrastructure β the failure is not in your code at all:
npm install/pip installfailed, the runner ran out of memory (OOM kill), a network fetch timed out, the compiler itself crashed. The log dies before your tests even start.
Two traps: do not trust a manual re-run that goes green β a passing retry proves nothing about the root cause and buries the flake for next time. And do not delete a test you suspect is flaky β quarantine it (see below) so the signal is not lost.
Detect
- Capture the full build log to a file β you cannot triage what you cannot re-read:
npm test 2>&1 | tee ci.logExpected: the full test output in your terminal and in
ci.log. On GitHub Actions, add one step after your test command:- name: Run tests run: npm test 2>&1 | tee ci.log - Find the first real error β everything after it is often collateral. Scan for the framework's failure markers:
grep -n -m1 'β' ci.log # jest first failure grep -n -m1 'FAILED' ci.log # pytest first failureExpected: one line number pointing at the first failing test, e.g.
47: β Cart pricing βΊ applies bulk discount. If the log dies duringnpm install, a Docker build, or a compiler step, this is infrastructure β skip the rest and fix the runner. - Check the failing test's history on the same commit. Did it pass here before? A failure on an unchanged commit is the strongest flake evidence there is:
cifd record --history .cifd-history.jsonl --commit $GITHUB_SHA < ci.log cifd historyExpected: one line per recorded run with its verdict. A test that failed on
abc1234but passed on the same commit earlier is a classic flake β no code change in between, so the code cannot be the cause. - Run the automated triage β first error, verdict, and repeats in one report:
npx ci-failure-detective analyze ci.log --format mdExpected output (from the tool's own demo on real fixture logs):
## π‘ CI Failure Detective β flaky **Category:** Test failure **Framework:** jest **Signature:** `a3f9c1e2b4d6` β seen 12Γ before (first 2026-10-18) **First error** (line 47): ``` β Cart pricing βΊ applies bulk discount over 10 items ``` **Failing tests:** - `Cart pricing βΊ applies bulk discount over 10 items` (src/cart/pricing.test.js) β **π‘ flaky** - Failed on abc1234 but passed on the same commit before β classic flake, no code change in between. **Suggested next steps** (advisory only β nothing is applied automatically): - **1 flaky test β quarantine suggested (not applied)** - Consider quarantining via --testPathIgnorePatterns or jest.retryTimes - Run tests verbose from now on so future triage has evidence. Same-commit flake detection needs per-test pass records, and plain logs only record failures:
npx jest --verbose # instead of npx jest pytest -v # instead of pytestWith verbose logs, a test that passes on a commit is visible; without them, same-commit evidence does not exist and verdicts fall back to flip-rate and regression heuristics.
Quarantine policy
What do you do with a test once you know it is flaky? Three practices from industry teams:
- Quarantine, don't ignore, don't delete. Google marks flaky tests so they report a failure only if they fail 3 times in a row, and teams re-run only the failing tests instead of the whole suite. The quarantine list is a tracked debt item that must shrink β a flaky test stays out of the blocking pipeline until its root cause is fixed.
- Fix the root cause, not the alarm. The most common sources are timing/synchronization problems (async waits), race conditions in concurrent code, shared state between tests, test-order coupling, environment drift, and flaky third-party services. Replace fixed sleeps with condition-based waits, isolate shared state, freeze clocks, seed randomness.
- Automatic re-runs are the last resort, not the first. Plugins like
pytest-rerunfailuresorjest.retryTimesretry a failing test a fixed number of times and report success if any attempt passes. They mask the symptom without fixing the cause, and they teach the team that green-after-retry means "fine" β which is how real failures start hiding.
Martin Fowler's warning from 2011 still holds: left uncontrolled, non-deterministic tests can completely destroy the value of an automated regression suite.
Limits
No detection method is perfect: a test that flakes once a month can pass 100 clean runs and still fail on release day β detection is a history question, not a single-run question. A flaky test can also be signal, not noise: a test that intermittently times out may be catching a genuinely slow service. Investigate before quarantining.
Our tool
ci-failure-detective is our local-first triage step: it reads a failed CI log and reports the first real error, a verdict per test β flaky, real, or unknown with a confidence score β and clusters repeats by a stable 12-character failure signature. A test is called flaky when it fails and passes on the same commit (0.95 confidence) or flips frequently across runs (0.70); a test that fails consistently where it used to pass is a real regression (0.80). With no history it answers unknown β it never guesses. It parses Jest, pytest, go test, and Mocha, and its failure classification works on any raw log: npm/pip/go install failures, tsc errors, Docker builds, OOM kills. It runs inside your runner β no logs leave your repo, no signup. Suggested actions (quarantine hints, retry guidance) are advisory only: quarantine stays suggest-only by design, because automatically retrying or silencing failures hides real bugs.
Honest comparison (checked 2026-10-24): BuildPulse, Trunk, Datadog, and Mergify ($18β99/month) give you dashboards, trend analytics, auto-quarantine, and alerts β at the price of uploading all your test data and logs to their cloud. Gradle Develocity adds AI-powered failure grouping and predictive test selection, enterprise sales-led. The closest open-source rivals are gh-failmap (failure clustering CLI, but GitHub-only and no flaky verdicts) and flakestat (local-first flaky hunting, but flakes only β no clustering, no log reading). LLM-based triage tools send your logs to a third-party API and bill per run. CI Failure Detective has no dashboard and no alerting β it is a local triage step, not a platform.
Sources
- Google Testing Blog, "Flaky Tests at Google and How We Mitigate Them" (May 2016) β testing.googleblog.com
- Amikoo, "Real Bug or Flaky Test? How to Diagnose Failures Before You Fix Anything" (October 2026) β amikoo.ai
- Qable, "Regression Testing Strategy: What to Re-Run in 2026" (October 2026), citing Memon et al., "Taming Google-Scale Continuous Testing" (ICSE-SEIP 2017) β qable.io
- ci-failure-detective README, techdevfun/ci-failure-detective, honest-comparison section (checked 2026-10-24)
Last updated: 2026-10-31. Edit on GitHub Β· Report an issue
β Previous guide Β· Next guide β
Still stuck? Get deployment help β