
The bug had already been closed three times.
Customers occasionally received two invoices after clicking Pay on a slow connection. The first fix disabled the button after the initial click. A few weeks later, duplicate invoices appeared through the mobile app, so another check was added there. Months later, the same complaint returned after an API integration retried a request when it did not receive a response quickly enough.
Each fix had worked.
None of them had solved the real problem.
The defect was not really "users can click the button twice." The deeper problem was that the system allowed the same billing operation to be processed more than once. Until that assumption changed, duplicate invoices could keep returning through different entry points.
This is why recurring bugs are so frustrating. A ticket can be resolved, tested and released successfully while the failure pattern that produced it remains inside the application.
Sometimes the exact code regression returns. More often, what appears to be "the same bug" is a related defect caused by the same architectural weakness, duplicated logic, missing validation, configuration gap or incomplete understanding of the original failure.
Google's Site Reliability Engineering guidance distinguishes between an incident's trigger and its underlying root causes, and uses structured postmortem analysis to identify recurring systemic patterns rather than repeatedly addressing isolated incidents. Google SRE: Postmortem Analysis
For Levelworks, recurring defects are particularly relevant to Error Free App Health, but they also expose weaknesses in Tested and Explainable & Debuggable. A healthy application should not merely make individual defects disappear; the way it is maintained should make the same classes of failure progressively less likely to return.
When users say, "That bug is back," there are several possibilities.
A later code change, merge or refactor removed or bypassed the fix.
This is a true regression.
The web application was corrected, but the mobile app, background worker or API still contains the same behaviour.
The original symptom returns through another route.
The team corrected the condition that exposed the problem rather than the weakness that allowed it.
The duplicate-invoice example belongs here.
"Checkout failed" might describe a database timeout this month and a payment-provider error next month.
Treating those as one recurring defect can send debugging in the wrong direction.
Before reopening the old ticket, compare:
Recurring symptoms deserve investigation, not automatic assumptions.
[Internal Link: “How to Find the Root Cause of Application Errors Faster”]
Production pressure encourages narrow fixes.
A specific input crashes a function, so engineering adds a condition preventing that input from reaching it.
That can be appropriate as an immediate correction, but the next question should be:
Why was invalid state able to reach this part of the application in the first place?
Suppose three screens independently validate a customer account before allowing an order.
One screen forgets a validation rule and causes a production defect.
Adding the missing condition fixes that screen.
The wider weakness remains: business validation is duplicated across several places and can drift again.
A stronger correction might move the rule into a shared domain or service layer where every path uses the same logic.
The difference is between:
Fix this incorrect condition.
and:
Remove the design weakness that makes this incorrect condition easy to recreate.
NIST's Secure Software Development Framework makes this principle explicit for software vulnerabilities, recommending that organisations address root causes so similar weaknesses are less likely to recur. The same reasoning applies more broadly to reliability defects.
A bug report contains information the test suite did not previously have.
Perhaps nobody knew that an empty address could reach a particular workflow, or that two requests could arrive simultaneously.
Once the failure is understood, that knowledge should usually become executable.
A useful sequence is:
The test becomes a permanent record of a condition the application is known to have mishandled before.
Google's engineering practices recommend including appropriate unit, integration or end-to-end tests with code changes and specifically ask reviewers whether those tests would fail if the code were broken. Google Engineering Practices: What to Look for in a Code Review
Martin Fowler's discussion of testing culture makes the same practical point: a bug fix without an automated regression test remains vulnerable to being undone later. Martin Fowler: Goto Fail, Heartbleed, and Unit Testing Culture
[Internal Link: “Regression Testing: How to Update an App Without Breaking Existing Features”]
Simply adding "a test" is not enough.
Suppose a checkout defect occurs only when:
A unit test around one function may pass while missing the behaviour that produced the bug.
Choose the test according to the failure boundary.
Defect
Useful regression protection
Calculation error
Unit test
Validation rule
Unit/domain test
Incorrect database interaction
Integration test
API contract failure
API/integration test
Authorization between user and resource
Integration/security test
Multi-service workflow
Integration or end-to-end test
Browser-specific user journey
Targeted UI/end-to-end test
The most valuable regression test is generally the smallest reliable test that reproduces the actual failure mechanism.
Too high a level and the suite becomes slow and fragile. Too low a level and it proves something different from the defect users encountered.
Duplicated business rules create multiple opportunities for the same defect.
Imagine discount eligibility implemented separately in:
One defect may be fixed in the web checkout while the same faulty assumption survives in four other places.
Searching the codebase for siblings of the bug is therefore an important part of fixing recurring defects.
After finding one failure, ask:
This turns a bug fix from a single-location patch into a controlled search for the wider defect pattern.
Where duplicated behaviour is genuinely the root problem, centralization may prevent future divergence.
A team may reproduce a bug in a simplified environment and fix what it sees there, while missing the conditions that actually matter in production.
Perhaps the defect requires:
If those details are absent from the reproduction, engineering may fix a related issue rather than the real one.
Good bug reports should therefore capture enough context to reconstruct the failure.
Useful details include:
This is where Explainable & Debuggable App Health becomes important. Better telemetry does not prevent the original defect, but it substantially improves the team's ability to determine whether the next report is truly the same failure.
Urgent production fixes are sometimes intentionally narrow.
That is reasonable.
If thousands of customers cannot complete an important transaction, the team may need the lowest-risk change that restores service rather than an architectural redesign during the incident.
The mistake is treating the emergency patch as the end of the work.
Google's engineering guidance explicitly recommends revisiting emergency changes afterwards for more thorough review. Google Engineering Practices: Emergencies
After production is stable, ask:
A hotfix is often mitigation.
Preventing recurrence may require another change.
Recurring defects are not always caused by fixes being too small.
Sometimes the response is too large.
A production bug triggers a substantial refactor involving several unrelated changes. Reviewers now have difficulty determining which modification actually addresses the defect, while the larger change creates additional regression opportunities.
Google's engineering practices recommend small, focused changes because they are easier to understand and review thoroughly and are less likely to introduce bugs. Google Engineering Practices: Small CLs
A recurring-bug fix should generally be as broad as necessary to remove the cause, but no broader.
That may mean changing several related areas when the weakness is systemic. It does not mean using every defect as an opportunity to refactor an entire subsystem.
Some defects do not live in application code.
A production problem may result from:
If engineering fixes the production environment manually but does not update the version-controlled source of that configuration, the next deployment can restore the old problem.
The defect appears to have "come back," but production has simply recreated the original condition.
Where practical, the correction should exist in the authoritative configuration or infrastructure definition, not only in the running environment.
This is one practical connection between Version Controlled and Error Free App Health.
If an important production fix cannot be traced to a controlled change, it is much easier for later deployments to erase it.
Sometimes the same area of an application produces defects because it has become unusually difficult to reason about.
Symptoms might include:
In such areas, individual bug fixes may continue to be correct while the surrounding structure keeps generating new defects.
At some point, repeated maintenance cost becomes evidence that local patching is no longer enough.
The right response might be targeted refactoring, clearer boundaries or replacement of a particularly fragile component.
That decision should follow evidence. One bug does not justify rewriting a subsystem, but a concentration of recurring defects can identify where technical debt is affecting application health.
[Internal Link: “How Technical Debt Affects the Health of Your Application”]
Bug trackers naturally organize work by issue.
That can hide patterns across issues.
Consider six tickets opened over four months:
These may have different components and owners, but the shared pattern suggests the system may not handle repeated operations safely.
Looking at the tickets individually encourages six patches.
Looking at them together may reveal one engineering capability the application is missing.
Google SRE uses structured information from postmortems to identify trends in root causes and triggers, allowing teams to prioritize broader improvements rather than treating every incident as independent.
Product teams can apply the same idea to defects.
Periodically group bugs by:
Recurring categories are often more informative than the total bug count.
"Fixed" should mean more than a pull request was approved.
For meaningful production defects, verify several things:
Run the exact reproduction against the corrected version.
Confirm it fails without the fix and succeeds with it where feasible.
Search for equivalent logic or entry points.
A merge sitting unreleased does not help affected users.
Use error rates, logs or the affected business workflow to make sure the expected result appears after release.
The final step matters because staging does not perfectly reproduce every production condition.
A technically correct change can still fail to solve the real customer problem if the team's understanding of the defect was incomplete.
For high-impact or repeatedly reopened defects, a small structured review can prevent another cycle.
Ask:
Question
What it reveals
Is this the exact same failure or a similar symptom?
Whether the old diagnosis still applies
What was the trigger?
What exposed the defect
What underlying condition allowed it?
The root cause
Where else can that condition exist?
Sibling defects
Which test would have caught it?
Missing regression protection
Why did existing tests miss it?
Test-suite weakness
Could later changes undo the fix?
Regression risk
What evidence will prove the fix in production?
Verification strategy
Does the broader system need improvement?
Structural prevention
This need not become a formal postmortem for every UI defect.
Use the depth of analysis appropriate to the impact and recurrence.
A bug that has returned for the third time deserves more investigation than another isolated typo.
Recurring bugs consume more than engineering time.
They damage confidence.
Users wonder why a supposedly fixed problem has returned. Support loses confidence in engineering's resolutions, and developers begin treating familiar areas of the application as dangerous to change.
Stopping that cycle requires a different definition of a good bug fix.
The strongest fix does four things:
It corrects the immediate defect, identifies the condition that allowed it, preserves that knowledge in tests or controls, and checks whether the same weakness exists elsewhere.
That is why Levelworks connects recurring-defect prevention not only with Error Free, but also with Tested and Explainable & Debuggable App Health. Tests preserve known failure conditions, while useful diagnostics help teams determine whether a later symptom is actually the same problem.
The objective is not to guarantee that no bug will ever resemble a previous one.
It is to ensure that when an application teaches the team something through failure, that lesson does not disappear when the ticket is closed.