Why bugs get reopened, and how to cut the rate
BUG-3120 was closed on Tuesday. On Thursday a tester reopened it. The developer's comment: "Works on my machine, the fix is in." The tester's comment: "Still broken on staging."
Both were right. The fix worked for the case the developer read. It did not work for the case the tester meant. Nobody wrote that case down.
Short version
- A reopened defect is a bug that was marked fixed, then found to be broken again during verification or later.
- Four causes explain most reopens: vague acceptance, partial fixes, missing regression cases, and environment mismatch.
- Reopens cost roughly twice the original fix, because two people context-switch back into work they had finished.
- A reopen rate of 5 to 10 percent is normal. Above 15 percent means something in the handoff is broken.
- The cheapest fix is writing the pass condition into the ticket before the developer starts.
- Track why each bug was reopened, not just how many. The count alone tells you nothing you can act on.
What counts as a reopen
Be precise, or your number will mean nothing. A reopen is a ticket that moved from a closed or resolved state back to an open state because the reported problem still happens.
These do not count:
- A new bug found in the same area. That is a new ticket.
- A ticket reopened for a documentation or comment change.
- A ticket reopened because the requirements changed after the fix.
Split those out with a "Reopen reason" field. If everything gets counted as a reopen, the metric becomes noise, and people start arguing about the number instead of fixing the cause.
Cause 1: the acceptance was never clear
This is the biggest one. The bug says what is broken. It does not say what "fixed" looks like.
Vague: "Discount code not applied correctly at checkout."
Clear: "With code
SAVE20on a 100 USD cart, the order total must show 80.00 USD before tax. Currently shows 100.00 USD. Fixed means the total updates within one second of applying the code, and the discount line appears in the summary."
The second version gives the developer a test they can run themselves. It also gives the tester the exact check to repeat. There is no room for two readings.
How to fix it
Add one line to every bug template: Fixed means:. Write it as a single observable statement with real values in it. Numbers, strings, and URLs beat adjectives.
| Weak pass condition | Strong pass condition |
|---|---|
| Page loads faster | Invoice list renders in under 2 seconds with 500 rows |
| Error message is better | Shows "Card declined. Try another card." instead of ERR_5012 |
| Login works | [email protected] can sign in on Safari 18 without a second prompt |
| Layout is fixed | Sidebar stays 240px wide at 1280px viewport width, no overlap |
Cause 2: partial fixes
The developer fixed the path you described. The bug lives on three other paths.
A user reports that the export button fails on the invoice list. The developer fixes the invoice list. The same export component is used on the customer list and the payments list, and both are still broken. The tester checks the customer list next week and reopens the ticket.
How to fix it
Ask two questions before the fix is closed:
- Where else is this code used? If the fix touched a shared component, name every screen that uses it in the ticket.
- What was the actual cause? A one-line answer is enough: "The export helper assumed
rowswas always an array. It is undefined when the list is empty."
That second answer usually reveals the other paths on its own. If the cause is "empty list", every screen with a list is a candidate.
Ask the developer to paste the changed file paths into the ticket. A tester who can see that utils/export.ts changed will know to check every export in the product, not just the one they reported.
Cause 3: the regression case was never added
A regression is a bug that comes back after being fixed. It comes back because nothing was watching that behaviour.
If a bug is fixed and no test covers it, the same bug will return whenever someone refactors that area. It might take three months. It will happen.
How to fix it
Make the test part of the definition of done for a bug fix, not a follow-up task. Follow-up tasks do not get done.
For each fixed bug, pick one:
- An automated test that fails on the old code and passes on the new code.
- A line added to the regression checklist for that module, with the exact steps and expected value.
- A note explaining why neither is possible — for example, a one-off data problem that cannot recur.
The third option is legitimate. Forcing a test onto every bug produces slow, useless test suites. What matters is that someone made the call on purpose.
Cause 4: environment mismatch
The fix works. It just is not where the tester is looking.
Common versions of this:
- The fix is merged but not deployed to the environment the tester uses.
- The tester's browser has an old bundle cached.
- Staging points at a different API version or a different feature flag setting.
- The tester's account has different permissions or different data than the developer's.
None of these are real defects, but they all produce reopens, and each one costs a full round trip of comments.
How to fix it
Record the environment on both sides. The bug report needs the environment where the bug was seen. The fix comment needs the environment and build where the fix was verified.
A workable minimum for both:
- URL, for example
https://staging.example.com - Build or commit, for example
2026.7.3-rc2ora91f3c7 - Browser and version, for example Chrome 138 on macOS 15
- Account used, for example [email protected]
- Any feature flags that are on
Collecting all of that by hand is tedious, and tedious steps get skipped. Tools like Crosscheck capture the URL, browser, console logs, and network requests automatically when you report a bug from the page, which removes the most common excuse for a missing environment section.
Then add one rule to verification: the tester states the build number they tested. "Still broken" is not a verification result. "Still broken on 2026.7.3-rc2, staging, Chrome 138" is.
A verification handoff that holds
Use these steps when a developer moves a bug to Ready for Test.
- The developer writes the cause in one sentence.
- The developer lists the files or modules changed.
- The developer names the build and environment where the fix is live.
- The developer confirms the "Fixed means" line still matches, or edits it and says why.
- The tester runs the original steps on the named build.
- The tester runs the two nearest related paths, then closes or reopens with a build number.
Step 6 is what catches partial fixes before the customer does.
Measuring the reopen rate without blame
The formula is simple:
Reopen rate = (bugs reopened in the period / bugs closed in the period) x 100
If 12 of 150 closed bugs were reopened in a sprint, the rate is 8 percent.
Treat this as a process metric, never a personal one. The moment a reopen rate appears on an individual's review, two things happen: developers argue every reopen, and testers stop reopening borderline cases. The number goes down and the product gets worse.
Instead, tag every reopen with one of the four causes above and review the split monthly. The split tells you what to change:
| Dominant cause | What to change |
|---|---|
| Vague acceptance | Add a required "Fixed means" field to the bug template |
| Partial fixes | Require the cause and changed files in the fix comment |
| Missing regression case | Make a test or checklist line part of done |
| Environment mismatch | Require build number on both report and verification |
Five reopens all caused by environment mismatch is a ten-minute fix to your template. Five reopens with five different causes is a different conversation. You cannot tell those apart from the count alone.
What a good rate looks like
Do not chase zero. A team with a zero percent reopen rate is usually a team where verification is a formality.
As a rough guide, 5 to 10 percent is healthy for most product teams. Under 5 percent, check that anyone is actually verifying fixes. Over 15 percent, work through the four causes in order — acceptance clarity almost always gives the biggest drop for the smallest effort.
Measure the trend over a quarter, not week to week. One bad sprint with a tricky module is not a signal.
Frequently asked questions
What is a good reopen rate for defects?
Most teams land between 5 and 10 percent of closed bugs. Rates over 15 percent usually point at unclear acceptance criteria or missing environment details rather than weak developers.
Who should be allowed to reopen a bug?
Anyone who can reproduce it on a named build. Restricting reopens to a single role slows the loop and pushes people to file near-duplicate tickets instead.
Should a reopened bug keep its original ticket number?
Yes, if the original problem still happens. Keeping one number preserves the whole history. File a new ticket only when the fix worked and a different problem appeared.
How do I stop arguments over whether a fix worked?
Write the pass condition with real values before the work starts. When the ticket says the total must read 80.00 USD, there is nothing left to debate.
Does a high reopen rate mean developers are careless?
Usually not. It more often means the handoff is thin: no cause written down, no build number, no clear pass condition. Fix the handoff before looking at people.




