ProductHow it worksPricingBlogDocsLoginFind Your First Bug
A single escaped checkout defect analyzed twice, as a five whys chain and as a fishbone diagram, shown side by side
TestingRoot Cause AnalysisFive Whys Analysis

Root Cause Analysis in Software Testing: 2 Methods, 1 Defect

Tom Piaggio
Tom PiaggioCo-Founder at Autonoma

Root cause analysis in software testing traces an escaped defect back past its symptom to the condition, or conditions, that let it ship undetected. Two techniques do most of the work: the five whys, a linear chain of cause-and-effect questions, and the fishbone diagram, which sorts evidence into categories like tests, environment, and process. Use the chain when one cause plausibly explains the defect, and the fishbone when you suspect more than one and can back it with evidence.

The retro is tomorrow morning. The incident closed two days ago, the fix already shipped, and nobody's arguing about whether it works. What's unresolved is the sentence written down as the cause, because that sentence decides whether the action item is "add a test," "fix the migration checklist," or both.

This is written for the person who has to produce that sentence: the QA engineer or SDET writing the post-incident review, briefing a junior on the defect, or answering an auditor who wants proof the team does this. It is not written for the QA lead deciding what a strategy document should say about root cause analysis, and not for the team arguing whether their AI-generated tests catch anything real. Both are real questions. Neither is this one.

The defect

Here's the incident, built with enough detail for the person writing this up, not the one designing next quarter's test strategy, to run both analyses honestly.

A subscription retailer's checkout flow supports two discounts on the same order: a percentage-off promo code, and a loyalty credit for logged-in members with an active tier. Six months earlier, the growth team decided the two should stack percentage-first, credit-second; the reverse order hands a bigger discount on every order using both, a gap modeling showed eroding margin faster than the loyalty program was worth.

Three weeks before the incident, the pricing service migrated its feature flags off a legacy YAML file onto a centralized config store. The migration script read every flag from the old file except those changed manually outside it, which is how the order-of-operations flag had been set six months earlier. The script never saw that flag's live value, so it wrote the store's original default, credit-first, over the margin-protection setting.

Every automated test kept passing: each suite checks its own discount type in isolation and gets the right number. Neither exercises a customer who has both; the two features shipped a year apart, built by different teams, with nobody owning the combination.

Nobody noticed for nine days. The bug threw no error and no support ticket, it quietly handed a slightly better deal to customers least likely to complain. It surfaced when finance's weekly margin reconciliation flagged rising discount depth for loyalty members using promo codes. That's the escaped defect both analyses below run on: not a crash, a quiet one, caught by a report instead of a page. Before analysis starts, this incident needs an intake record with real expected and actual results attached, which is what a bug report needs.

Five whys on this defect

Five whys is a linear chain: state the symptom, ask why, treat the answer as the new symptom, and ask why again, five times or until something actionable turns up. Here's the five whys analysis run straight through.

Why did loyalty members who also used a promo code get a bigger discount than intended? Because the discount engine applied the loyalty credit first and computed the percentage discount on what was left, instead of the other way around.

Why was the order of operations reversed? Because the discount-order-of-operations flag reverted to its old value, credit-first, during the pricing service's config migration three weeks earlier.

Why did the migration reset a flag changed deliberately six months earlier? Because the migration script only picked up flags present in the legacy YAML file, and this flag had been set manually outside that file, so the script never saw the live value and fell back to the store's default.

Here's the fork. Two threads open here, and both are legitimate: why the flag's real value lived outside the file the migration trusted, a configuration-ownership question, or why nothing caught the wrong value before release, a test-coverage question. This chain follows the second, because it has a concrete artifact behind it. The first thread doesn't get walked, and that's the seed of the failure mode this technique carries: it commits to one path, and the other simply doesn't get investigated in this pass.

Why didn't anything catch the wrong flag value before release? Because the suite tests each discount type in isolation, and an order-of-operations defect is invisible to a test that only ever exercises one type at a time.

Why was there no test for the combined case? Because the two features were built a year apart by different teams, each with its own scoped suite, and once both existed, nobody owned testing the combination.

Five steps, one thread, one answer at the bottom: a coverage gap between two features each tested well on their own, and a configuration-ownership question the chain never returned to.

The fishbone diagram categories, on the same defect

A fishbone diagram, also called an Ishikawa diagram, runs the opposite direction. Instead of following one thread deep, it sorts the same evidence across categories, looking for more than one contributing factor at once. Six categories cover most software incidents: code and design, tests and test data, environment and configuration, process and review, tooling and observability, and people and handoff. The discipline is filling only the categories the evidence supports, and saying so when one comes up empty.

The same checkout discount-stacking defect analyzed twice. On the left, the five whys drawn as a vertical ladder of five steps running from the deeper-than-intended discount down through the reversed credit ordering, the reverted flag and the migration script to the missing combined-case test. On the right, a fishbone diagram with six category branches feeding one spine: environment and configuration, tests and test data, process and review, people and handoff and tooling and observability are each filled and marked, while code and design is drawn as a dashed empty box labeled as having no evidence behind it
Same defect, same evidence. The chain hands you one sentence; the fishbone hands you a map with one box honestly blank.

Environment and configuration. Filled. The discount-order-of-operations flag reverted to a stale default during the config-store migration, because the migration script never saw the value that had been set outside the legacy file.

Tests and test data. Filled. No test exercised a loyalty member applying a promo code. This isn't a flakiness problem, both isolated suites ran reliably and still missed the defect (see our guide to debugging flaky tests for that different failure mode); it's a coverage gap in what the suites were ever asked to check.

Process and review. Filled. The migration's pull request was reviewed for whether the script ran correctly, not whether the resulting flag values matched production. Nobody diffed the config.

People and handoff. Filled. The two features were owned by different teams a year apart, and neither inherited responsibility for testing them together once both existed.

Tooling and observability. Filled. Nothing alerted on discount-depth drift. A weekly manual report was the only thing watching, which is why nine days passed.

Code and design. Left empty, deliberately. The discount computation did exactly what its configuration told it to do, correctly, for either ordering. Filling this category anyway, just to look thorough, would manufacture a finding the evidence doesn't support, which is exactly the failure mode this technique invites.

Five whys vs fishbone: what each one surfaced

Run on the same defect, the two techniques don't disagree with each other, they surface different slices of the same evidence.

Animated diagram showing an incident moving through evidence reconstruction before a team chooses a five-whys chain or fishbone analysis and assigns an owner.

The animated handoff reinforces the operating order: reconstruct the incident from evidence first, then choose the analysis shape the evidence can support.

Five whysFishbone
What it foundThe config revert, one thread deepConfig, tests, process, people flagged
What it missedThe ownership gap, never revisitedWhich factor mattered most
Artifact left behindA five-step causal narrativeA six-category evidence map
Who it convincesWhoever owns the thread you pickedWhoever wants breadth over a verdict
How long it takesFifteen minutes, one clear write-upAn hour, six categories to fill

The chain reads better in a postmortem doc, because it commits to an answer. The fishbone reads better in a review meeting, because it shows the room that nobody's evidence got ignored. Neither one is more rigorous than the other. They're built for different readers, and this defect had evidence for both.

How Autonoma shortens the evidence-gathering step

Most of the work in the two sections above wasn't analysis, it was reconstruction. Finding the flag's stale value, confirming which suite ran against which discount type, establishing that the migration script never read the manually-set flag: that's evidence-gathering, and on a real incident it takes longer than either technique takes to fill in once the evidence already exists.

That's the piece of a retro our own agents remove, not the analysis itself. They read the codebase directly, the routes, the components, and the flows connecting them, and plan end-to-end test cases from that reading, including the combined paths a team like this one would otherwise leave to chance: a loyalty member who also has a promo code, not just each discount type tested alone. When one of those generated tests runs against a preview environment and fails, the failure doesn't arrive as a symptom you have to trace backward. It arrives already carrying the trace of what happened in the browser, the environment it ran in, and the code and configuration live for that run, because our Diffs Agent ties test maintenance to the diff that changed the codebase in the first place. Each run's results are also classified automatically, separating a real behavioral regression from an agent error or a stale test-plan mismatch, so that call doesn't default to a human either. It's the same category of evidence you'd otherwise assemble by hand when debugging a production incident after the fact.

None of that replaces the retro. Autonoma doesn't run the postmortem, doesn't decide which of two legitimate threads is "the" root cause, and doesn't make the call on whether a configuration-ownership gap or a testing gap gets the action item first. Those are judgment calls a person makes with the evidence in front of them. What changes is how much of the hour goes to assembling that evidence instead of arguing about what it means.

When each one fails

Neither technique is universal; treating both as always-applicable is picking by habit, not by the evidence's shape.

Five whys fails on defects with more than one independent cause: a linear chain walks one thread. This incident is the demonstration: the config revert and the missing test are independent factors, not links in one chain, and either alone would have prevented it. The chain picked the test thread and never returned to the configuration-ownership thread, so a team stopping there names one root cause and leaves the other live.

How each technique fails on the same defect. On the left, a five whys ladder runs three steps down and then forks: the followed branch continues in solid lines to the missing combined-case test and the unowned feature combination, while the abandoned branch is drawn in dashed grey, asking why the flag lived outside the migrated file and marked as never walked in this pass. On the right, a fishbone with all six categories filled in and highlighted, including code and design, its spine drawn as a dashed line ending in an empty box for a ranked cause and an owner that nobody assigned
Each one fails on its own terms. The chain drops a thread it never returns to; the fishbone fills every box and ranks nothing.

Fishbone fails the opposite way, by refusing to leave any category empty rather than picking one thread. A team under pressure to look thorough fills all six regardless of evidence, and code-and-design is where a blank box looks like an oversight. A box in every category, no ranked cause. It looks like rigor. It isn't. Rigor is the empty box.

The selection rule follows: chain for one plausible cause and a short write-up; fishbone when you suspect more than one and have evidence for it, not a hunch. Neither substitutes for the evidence itself; by the time RCA starts, the ticket has usually moved past the states a defect life cycle diagram tracks, since RCA happens after the fix ships.

Root cause or contributing factor?

A contributing factor had to be true for the defect but wasn't sufficient alone. A root cause is the one factor that, fixed, would have prevented the incident outright. Those collapse on a single-cause defect. Not here: the revert and the missing test are both necessary, neither sufficient, and naming one "the" root cause is a prioritization call, not an analytical one. Multi-cause incidents call for naming factors, plural, with an owner each, not the dashboard number defect density or defect leakage produces instead.

Write the retro with whichever technique fits, noting when you used the other and why. That reconstruction problem doesn't disappear because you picked the right method, which is why we built Autonoma to generate and run behavioral tests directly from a codebase: not to write the retro, but so the next cross-feature flow gets tested before it ships instead of reconstructed after it escapes.

Frequently Asked Questions

Root cause analysis in software testing traces an escaped defect back past its immediate symptom to the condition, or conditions, that let it reach production undetected. The goal isn't just identifying what broke, it's identifying what has to change so the same class of defect can't ship again. Five whys and the fishbone diagram are the two most common techniques, surfacing different evidence from the same incident.

State the symptom as your first question, ask why it happened, and treat that answer as the new symptom for the next why. Repeat five times, or until you reach an answer that's actually actionable, not another restatement of the problem. The technique works cleanly on one plausible cause; when a chain reaches a point where two questions could follow, whichever thread you pick gets investigated, and the other doesn't get walked.

A fishbone diagram, also called an Ishikawa diagram, sorts evidence into categories, such as code and design, tests and test data, environment and configuration, process and review, tooling and observability, and people and handoff, instead of following one causal chain. It's built to surface more than one contributing factor at once. The discipline is filling only the categories the evidence supports and leaving the rest empty; filling every category just to look thorough produces a diagram with no ranked cause.

Use five whys when the evidence points at one plausible cause and you want a short write-up. Use a fishbone when you suspect more than one contributing factor and have evidence for more than one, not a hunch. A linear chain forces a single narrative onto a multi-cause defect, and a fishbone on a single-cause defect just takes longer to reach the same answer.

A contributing factor is a condition that had to be true for the defect but wasn't sufficient by itself. A root cause is the one contributing factor that, if fixed, would have prevented the incident outright. That distinction collapses in single-cause defects, where the two are the same thing, but in multi-cause defects, calling one 'the' root cause is a prioritization decision, not an analytical one; it's more honest to name the contributing factors, plural, with an owner for each.

Autonoma can make root cause analysis faster by giving the investigation evidence from planned end-to-end checks in a live preview environment. Its Planner and Diffs Agent analyze the codebase and PR changes to keep coverage aligned, the Executor runs the checks, and the Reviewer evaluates the result. Bring that evidence into the retro to reconstruct the failing flow before choosing five whys or a fishbone. The team still owns the root-cause conclusion and the action it takes, but it can begin with a more concrete record of the failure.

Related articles

A horizontal agent trajectory diagram showing a tool call passing a right-tool checkpoint but failing an argument-accuracy checkpoint

How to Test AI Agents That Take Actions (Tool Calls)

A runnable guide to testing tool-calling agents: right tool, right order, right arguments, mocked vs live calls, failure handling, and non-determinism.

A chatbot test pipeline moving from manual QA through scripted and semantic assertions into an automated CI gate that samples the model N times before allowing a merge

Chatbot Automation Testing: Why Assertions Fail

Chatbot automation testing that survives non-deterministic replies: the migration to a CI gate, n-run sampling, threshold gating, and real GitHub Actions YAML.

Sealed tenant data capsules being sorted into fully partitioned vault compartments, each isolated from the others, illustrating multi-tenant test data isolation

Multi-Tenant Test Data Isolation

What multi-tenant test data isolation means, why it matters for testing, and the four isolation patterns (schema, row-level, database, per-run) with tradeoffs.

A single disposable tenant boundary spun up inside one shared database, seeded, tested against, and then discarded, next to a separate full database fork labeled as a branch

What Is a Throwaway Tenant? (Disposable Tenants for Safe Testing)

A throwaway tenant is a disposable, isolated tenant created for one test run, then torn down. How it differs from a database branch.