Filing a Bug Report That Gets Fixed
A minimal reproduction, the expected and actual behaviour, the environment, and one bug per report — the difference between a ticket that gets fixed and one that gets closed.
A minimal reproduction, the expected and actual behaviour, the environment, and one bug per report — the difference between a ticket that gets fixed and one that gets closed.
Two reports were filed about the same defect on the same morning. One said "Import broken — some records were missing, please fix." It was closed nine days later as cannot reproduce.
The other attached a two-row file and said which row had the bad date. It was fixed that afternoon.
Same bug, same tester, same product. Everything up to this point — the risk thinking, the boundary analysis, the exploratory session — turns into a change in the software or it does not, and the report is the hinge.
By the end of this lesson you will know exactly what goes into one, how to shrink a reproduction until it cannot be argued with, and how to write about a defect without making it about a person.
Three jobs. Every rule in this lesson serves one of them.
Somebody else, on another machine, can see it.
A defect that cannot be reproduced cannot be fixed, and usually is not believed either.
Somebody can decide: now, later, or never.
That decision needs to know what it costs to leave it alone, which is a thing only you can tell them.
A search turns it up in a year.
When somebody wonders whether this behaviour is intentional, your report should be what answers them.
Notice what is not on that list: diagnosing the cause. It is welcome when you have it, it is not your job, and a wrong guess is worse than none — a report titled "caching bug" sends somebody to the wrong place for an hour.
Title Reset link works a second time after being used
Environment Staging, build 2.14.3, Chrome 131 / macOS 15
Account: alice@example.com (verified, standard role)
Steps 1. Sign in as alice@example.com and sign out
2. On the login page choose "Forgot password"
3. Enter alice@example.com and submit
4. Open the link in the email; set the password to
"firstchange1"
5. Confirm the success message appears
6. Open the SAME link from the email again
Expected Step 6 shows "This link has already been used" and
offers to send a new one. No password change is
possible.
Actual Step 6 shows the password form again and accepts a
new password. The password is changed a second time.
Impact A reset link stays valid indefinitely after use. Anyone
with access to the email — a shared inbox, a forwarded
message, an old backup — can change the password at any
later time.
Severity High Priority (for triage to set)
Evidence screenshot-1.png, screenshot-2.png, har-log.har
Notes Same behaviour in Firefox. A link that has expired is
correctly rejected, so expiry works and single-use
does not.Each field earns its place.
Title — one sentence describing the behaviour, specific enough to recognise in a list of two hundred. "Reset link works a second time after being used" is findable; "Password bug" is not.
Environment — where you saw it. Build or version, browser, operating system, and the account used. Half of "cannot reproduce" is somebody trying on a different build with a different account.
Steps — numbered, starting from a state anyone can reach. Real data, written down. No step that requires a decision from the reader.
Expected and actual — separately, and this is the part people collapse into one. "The link should be single use" is not an actual result. Expected is what should happen and why you believe that; actual is what did.
Impact — the reason to care, in terms of consequence rather than severity language. Who is affected, how often, what could go wrong. This is what makes triage possible.
Evidence — a screenshot, a screen recording for anything involving sequence or timing, the browser's network log, the relevant server log lines.
Notes — what you tried that narrows it. "Also in Firefox" rules out one browser. "Expiry works, single-use does not" points at the mechanism.
The reproduction you first found is rarely the shortest one, and time spent shortening it pays for itself several times over.
The method is to remove one thing at a time and check whether it still happens.
Take a step away
Any step. The colleague you invited, the row you edited, the export you did before the import.
Try again
Still happens? That step was not part of it — leave it out for good and take another away.
Stops happening?
Then you have found the trigger. Put that one step back and keep removing the others.
Seven steps become one: import a two-row CSV where row two has an invalid date. And notice what fell out of the process — the trigger, named, without reading a line of code. That is the usual outcome. Minimising and diagnosing turn out to be the same activity.
It also makes the report much harder to dismiss. A one-step reproduction with a two-line file attached is not something anyone argues with.
The exploratory session in the last lesson found five defects in the import feature. That is five reports.
Bundling is tempting and breaks everything downstream. Each defect may have a different cause, a different severity, and a different owner. A single ticket cannot be half-fixed, half-verified, or partly closed — so the two easy ones get fixed, the ticket stays open for months, and nobody can tell what state the third one is in.
If several reports share a cause, link them. That is what related-issue links exist for.
Two different things, routinely conflated, and the next lesson is entirely about them. The short version for filling in a report:
Severity is how bad the behaviour is. Data loss, security exposure, a crash, a broken core flow, a cosmetic imperfection. You are usually best placed to judge this.
Priority is when it gets dealt with. That depends on the release plan, the customers affected, the commercial context, and what else is in flight. Usually not yours to set, and stating impact clearly is how you influence it.
So: set severity, describe impact, and leave priority to triage.
Bad — cannot be reproduced, cannot be assessed:
Title Import broken
Body Tried importing a CSV and it didn't work properly. Some
records were missing. Please fix.Good — one step, real data, stated consequence:
Title Failed CSV import leaves partial data and reports
"Import failed"
Environment Staging, build 2.14.3, Chrome 131 / macOS 15
Steps 1. Import the attached 2-row CSV (row 2 has the date
"31/02/2026")
Expected Either no rows are imported and the message says so,
or valid rows are imported and the message names the
row that failed.
Actual Row 1 is imported and saved. The message says
"Import failed. No records were imported."
Impact The user believes nothing was imported and re-runs
the file, duplicating every row that succeeded. Seen
in production support tickets #4412 and #4530.
Severity High
Evidence 2-row-bad-date.csv, screenshot.png
Notes Row order matters: with the bad date in row 1, nothing
is imported and the message is correct.The bad version costs a round trip at minimum, and often three: which file, which records, what did you expect, which environment. Each round trip is a day, and after two the report is stale and gets closed. Worse, "didn't work properly" invites the reading that the reporter made a mistake — so the default outcome is no fix.
The good version can be acted on immediately and is much harder to misjudge. The Impact line links it to real support tickets, which is what moves it up a priority list.
A defect report is read by the person who wrote the code. That does not mean softening the finding — it means writing about the software rather than the author.
say not
"Row 1 is imported and the message "This was clearly never
says nothing was imported" tested"
"Expected per the requirement, "Someone did not read
section 4.2" the spec"
"Not reproducible on the previous "You broke this in the
build; introduced between 2.14.2 last release"
and 2.14.3"The left column is more useful as well as less hostile — it contains the information. "Introduced between 2.14.2 and 2.14.3" is a genuine finding that narrows the search; "you broke this" is not.
One more thing worth stating plainly: sometimes the behaviour is intended and you were wrong. That is a normal outcome, it means the requirement was unclear, and the right response is to get the intended behaviour documented so the next person does not report it too.
Two responsibilities that are easy to drop.
Verify the fix properly. Re-run your reproduction, and also the neighbourhood — the sanity check from two lessons ago. A fix that stops the second use by invalidating all links passes a re-run of your steps and is a worse bug than the one it replaced.
Make sure it leaves a test behind. A defect that reached working software found a gap in the tests, and the fix should close the gap as well as the symptom. Ask, in the ticket: is there now a test that fails against the old behaviour? That question is the difference between fixing this bug and preventing its return.
# Three jobs of a report
reproducible someone else, another machine, can see it
assessable someone can decide fix-now / later / never
findable in a year, a search turns it up
# diagnosing the cause is a bonus, not the job — and a wrong
# guess in the title costs an hour
# Anatomy
Title one specific sentence; findable among 200
Environment build/version, browser, OS, the account used
Steps numbered, from a reachable state, real written-down data
Expected what should happen, and why you believe that
Actual what did happen — separately from Expected
Impact who is affected, how often, what it could cost
Severity how bad the behaviour is (yours to judge)
Priority when it gets done (triage's to set)
Evidence screenshot; a recording for timing; network and server logs
Notes WHAT YOU TRIED THAT NARROWS IT — the best line in the report
# Minimise the reproduction
# remove one step, check it still happens, repeat
# the step you cannot remove is the trigger
# minimising and diagnosing are usually the same activity
# a one-step repro with a 2-line file is not argued with
# One bug per report
# bundled tickets cannot be half-closed, so they rot
# link related ones instead
# Write about the software, not the author
say "row 1 is imported and the message says nothing was"
not "this was clearly never tested"
say "not reproducible on 2.14.2; introduced in 2.14.3"
not "you broke this last release"
# Cannot reproduce it now?
# file it as a question and say so. Do not file it as a defect.
# After the fix
# re-run your steps AND the neighbourhood (a fix can break adjacent
# behaviour and still pass your repro)
# ask whether a test now exists that fails against the old behaviourThe next lesson takes the two fields you deliberately left apart — severity and priority — and looks at why they come apart, who owns each, and how to argue for a fix with evidence rather than insistence.
Before that, take a bug report you have written or received and check it against the anatomy above. The missing field is almost always Impact, and adding it is usually what changes whether the thing gets fixed.