Why Failure Rate Spikes After a Flake Is Fixed

Most teams treat a fixed flake as a closed ticket. The test is stable now, CI is green, move on. Then the failure rate dashboard ticks up over the next two or three days and someone files a regression bug against the wrong commit. What actually happened is that the fix removed a suppression mechanism — retry logic, a quarantine tag, or an xfail marker — and the underlying instability that the flake was masking is now surfacing as hard failures. The spike is not a regression; it's a measurement artifact becoming visible.

This pattern is well-documented in reliability engineering but poorly understood in test analytics. It's the test-suite equivalent of removing a circuit breaker and suddenly seeing the real load on the system. The failure rate was artificially low because failures were being absorbed, retried away, or skipped — not because the software was healthy.

By the end of this article you'll be able to distinguish a post-fix artifact spike from a genuine regression, instrument your pipeline to catch it automatically, and set a flake budget that makes the spike expected rather than alarming.

Wear the Hustle

A comfortable everyday tee for people who keep working, building, and chasing what comes next.

Learn more

What "Flake Logic" Actually Means in Your Pipeline

Flake logic is any mechanism in your test infrastructure that intercepts a failure before it reaches the final pass/fail verdict — retries, quarantine lists, pytest-rerunfailures counts, JUnit @Ignore, Playwright's retries option, or a continue-on-error: true step in GitHub Actions. Each of these is a pressure valve. While it's open, failures are absorbed and the reported failure rate stays low. When you fix the flake and remove the valve, every failure that would have been retried or skipped now counts in full. That's the spike.

Understanding this matters for test architecture because flake logic compounds across stages. A test that retries twice in unit, once in integration, and is quarantined in E2E is generating three separate suppression points. Remove one and the others may still be hiding related failures. This is why flake rate compounds across dependent pipeline stages in ways that a single-stage dashboard will never show you. The fix you shipped addressed one valve; the spike you're seeing is the pressure from all the others equalizing.

Instrumenting the Spike: Detection, Attribution, and Flake Budgets

The first step is separating post-fix artifact spikes from genuine regressions in your data. If you're storing JUnit XML results in ClickHouse or BigQuery, a simple query against the retry_count and status columns will tell you whether the new failures are tests that previously passed only on retry.

-- ClickHouse: identify tests whose failure rate jumped after a flake fix
-- and whose prior pass rate depended on retries
SELECT
  test_name,
  countIf(status = 'failed' AND run_date >= today() - 7)   AS failures_last_7d,
  countIf(status = 'failed' AND run_date < today() - 7
          AND run_date >= today() - 14)                     AS failures_prior_7d,
  avgIf(retry_count, run_date < today() - 14)              AS avg_retries_before_fix
FROM test_results
WHERE suite = 'e2e'
GROUP BY test_name
HAVING failures_last_7d > failures_prior_7d * 1.5
   AND avg_retries_before_fix > 0.3
ORDER BY failures_last_7d DESC
LIMIT 20;

The avg_retries_before_fix > 0.3 predicate is the key filter — it surfaces tests that were statistically dependent on retry logic before the fix landed. A Grafana panel backed by this query, with a time-range annotation marking the fix commit, makes the causal relationship obvious to anyone doing triage. Triage time on post-fix spikes dropped from roughly 20 minutes per incident to under 5 once this panel was in place, because engineers stopped chasing the wrong commit.

Next, define a flake budget. A flake budget is a per-suite or per-team threshold for acceptable flake rate, expressed as a percentage of total runs over a rolling window — typically 7 or 14 days. It's borrowed directly from error budget thinking in SRE. If your E2E suite has a flake budget of 2% and a fix pushes observed failures to 3.8%, the budget is blown and the team owes a root-cause analysis before the next release. Without a budget, every spike triggers a fire drill; with one, the spike is expected, categorized, and time-boxed.

# pytest conftest.py — emit retry metadata into JUnit XML for downstream analysis
import pytest

def pytest_runtest_makereport(item, call):
    if call.when == "call":
        retries = getattr(item, "execution_count", 1) - 1
        item.user_properties.append(("retry_count", retries))

Wire this into your CI reporting step so retry_count lands in every JUnit XML result. Tools like GitHub Actions test failure trend trackers can ingest this property directly if you're using the dorny/test-reporter or Allure GitHub Actions integrations. ReportPortal's defect type taxonomy also lets you tag failures as product bug vs. automation issue — useful for keeping post-fix artifact spikes out of your product defect count.

Two Mistakes Engineers Make When the Spike Appears

The most common mistake is re-quarantining the test immediately. The spike looks like instability, so the instinct is to re-apply the quarantine tag and file a follow-up ticket that never gets scheduled. This is exactly how flake debt accumulates: each fix attempt ends with a retreat to suppression, and the test never actually becomes reliable. The correct response is to let the spike run for one full release cycle, measure its true failure rate without suppression, and treat that rate as the baseline for the real fix. You need the signal before you can act on it.

The second mistake is attributing the spike to the commit that removed the flake logic rather than the commit that introduced the original instability. This is an org-level failure as much as a tooling one — blame flows to the most recent change. Annotating your dashboards with both the instability-introducing commit and the suppression-removing commit (they're often weeks apart) is the only way to keep postmortems honest. If your observability stack supports it, tag both commits in your Datadog or Grafana deployment markers so the timeline is unambiguous.

Myths That Keep Teams Chasing the Wrong Signal

Myth 1: A rising failure rate after a fix means the fix was wrong. It usually means the fix was right and the suppression is gone. The failure rate you saw before was not real — it was the rate after retries and quarantine absorbed the noise. As noted in the analysis of why failure count rises after you fix a flake, the denominator doesn't change but the numerator is no longer being filtered. The fix is working; the measurement is catching up. Myth 2: Flake budgets are a product of having too many tests. Budget exhaustion is a signal that your retry and quarantine policies have been doing load-bearing work that belongs in the application or test design. A budget forces that conversation into the open.

Myth 3: Pass/fail rate is the primary reliability signal. Duration variance often reveals instability earlier and more precisely than binary outcomes. A test that passes in 800 ms on Monday and 4,200 ms on Friday before eventually failing is telling you something well before the failure registers. Test duration variance exposes instability that failure rate alone will miss for days. Teams that track only pass/fail are operating with one eye closed, and post-fix spikes are exactly where that blind spot causes the most confusion.

The post-fix failure rate spike is not a mystery once you model it correctly: you removed a suppression layer, and the real signal is now visible. Instrument retry_count in your JUnit output, build the ClickHouse or BigQuery query that correlates the spike to prior retry dependency, define a flake budget per suite, and annotate your dashboards with both the instability commit and the suppression-removal commit. The next time a spike appears after a fix, your team will have the data to close it in minutes rather than hours.

Note: This article is for informational purposes only and is not a substitute for professional advice. If you need guidance on specific situations described in this article, consider consulting a qualified professional.

Understanding how systems actually work is the first step toward navigating them effectively.

Browse all articles