Every developer has a backlog with the same category of ticket in it: "Login is broken sometimes." No device, no steps, no timestamp. Nobody can act on it, so nobody does. It sits there until someone closes it during a cleanup sweep.
The bug was probably real. The report just failed to survive contact with the person who had to fix it.
Good bug reporting is not about writing more. It is about writing the specific things that let someone else recreate the failure on their machine. Here is the structure that works, and why each part earns its place.
The anatomy of a report that gets fixed
1. A title that describes the failure, not the feeling
Compare:
- ❌ "Checkout is broken"
- ✅ "Checkout: 'Pay' button stays disabled after entering a valid card on iOS 18"
The second one lets a developer decide whether it is theirs, whether it is urgent, and whether it duplicates something already open — without opening the ticket. Titles get skimmed dozens of times and read carefully once. Optimise for the skim.
2. Environment, always
The single most common reason a bug cannot be reproduced is that the reporter and the developer were not in the same conditions. At minimum:
- Device model and OS version — "iPhone 14, iOS 18.2", not "iPhone"
- App version or build number
- Network conditions if relevant — wifi, cellular, offline
- Account state — new user, existing user, logged out, specific plan
Device fragmentation means "works on my machine" is usually true and usually irrelevant.
3. Numbered steps, starting from a known state
Steps should begin somewhere the developer can reach. "Open the app while logged out" is a known state. "From the settings screen" is not — how did you get there, and with what account?
1. Fresh install, launch app
2. Sign in with an account that has no saved payment method
3. Add any item to cart
4. Tap Checkout
5. Enter card 4242 4242 4242 4242, any future expiry, any CVC
6. Tap PaySix numbered lines beat three paragraphs of prose every time.
4. Expected vs. actual, stated separately
Expected: Payment processes and the confirmation screen appears. Actual: The Pay button greys out and nothing happens. No error message. The app stays on the checkout screen indefinitely.
This looks pedantic until you meet the case where the developer thinks the current behaviour is correct. Writing the expectation down surfaces the disagreement immediately instead of three rounds of comments later.
5. Evidence
A screenshot showing the stuck state. Better, a screen recording showing the steps. Better still, a recording plus the relevant log output or a crash trace.
Annotate screenshots. A red circle around the element in question removes an entire round of "which button?"
6. Frequency and severity
- Frequency: every time / roughly 1 in 5 attempts / once, not reproduced since
- Severity: blocks the core flow / has a workaround / cosmetic
Intermittent bugs are worth reporting — but say they are intermittent. A developer who tries once, cannot reproduce, and closes the ticket has been misled by a report that implied determinism.
What to leave out
Diagnosis you are not sure about. "I think it's a caching issue" sends developers down a path. If you have real evidence, include the evidence. If it is a hunch, mark it as one.
Multiple bugs in one ticket. Three problems in one report means two get forgotten. One report, one bug.
Emotion. "This is completely unusable and has been broken for weeks" adds no information and makes the report easier to dismiss. The facts are more damning than the adjectives anyway.
The intermittent bug problem
Reports that only reproduce sometimes are the hardest and the most valuable, because they are the ones that reach production. When you hit one:
- Record the timestamp. It lets someone correlate against server logs.
- Note what was different. First launch of the day? After backgrounding? On cellular? Low battery? Intermittent bugs usually have a hidden condition, and the reporter is the only person who was there.
- Try to narrow it. Even "happens on cellular, not on wifi" converts an unfixable ticket into a solvable one.
- Report it anyway. An imperfect report of a real intermittent bug beats silence.
A template worth stealing
TITLE: [Area]: [what fails] on [condition]
ENVIRONMENT
Device: Pixel 7, Android 15
App version: 2.4.1 (build 388)
Network: Wifi
Account: New user, free plan
STEPS
1.
2.
3.
EXPECTED
ACTUAL
FREQUENCY: Every time / 1 in N / Once
SEVERITY: Blocker / Major / Minor / Cosmetic
EVIDENCE
[screenshot / recording / logs]Paste it into your issue tracker as a template. The structure does the remembering so reporters do not have to.
Why this matters more than it looks
A vague bug report is not a neutral event. It costs a developer twenty minutes of failed reproduction, it teaches the team that reports are low-value, and it usually ends with a real bug being closed as unreproducible — which means a real user hits it again next month.
The structure above takes an extra two or three minutes per report. It routinely saves an hour on the other end.
This is a large part of what you are actually buying when you outsource testing: not just people who find problems, but reports written so the fix is the only work left. Bugfed reports arrive with device, build, steps, expected/actual and screenshots already in place — structured, not a screenshot dumped in a chat thread.