How to file a bug report developers actually want to fix
A great bug report leads with the title and the steps to reproduce, then layers on evidence — get those six fields right and tickets stop bouncing back with 'need more info.'
A great bug report has 6 things — in this order
A great bug report contains six things: a specific title, numbered steps to reproduce, expected vs actual results, the environment, evidence, and a severity signal. The steps to reproduce matter most — if a developer can reproduce the bug on the first try, it almost always gets fixed. Everything else exists to make first-try reproduction possible.
A great bug report leads with the title and the steps to reproduce, then layers on evidence — a screenshot or replay, console and network logs, the environment, and an explicit expected-vs-actual pair. Get those six fields right and developers stop bouncing your tickets back with "need more info." Get them wrong and your bug sits in triage limbo while everyone burns time on clarification.
This isn't a matter of taste. The organizations that process the most bug reports on earth converge on the same short list. Mozilla's Bug Writing Guidelines state plainly that steps to reproduce are "the most important part of any bug report" — and that if a developer can reproduce the bug, it is "very likely to be fixed." Atlassian's Jira bug-report guidance calls the numbered reproduction steps "the most vital section." The most-cited essay on the subject, Simon Tatham's How to Report Bugs Effectively, reduces the whole discipline to one sentence: "The aim of a bug report is to enable the programmer to see the program failing in front of them."
Why so many bug reports fail
Most weak bug reports fail one of three ways:
- Missing or vague repro steps. A one-liner like "the page broke" with no path to reproduce forces the developer to hunt through your account history before they can even start. If the steps are unclear, Mozilla notes, "it might not even be possible to know whether the bug has been fixed."
- Wrong altitude. Reports either over-describe ("on Tuesday I clicked the green button while listening to Spotify…") or under-describe ("checkout broken"). Tatham's advice is to separate facts — "I was at the computer and this happened" — from speculation — "I think the problem might be the cache." Leave out speculation if you must; never leave out facts.
- No evidence. When something is intermittent, race-condition-y, or purely visual, words alone can't convey what the user saw.
The cost is real and it compounds. Ministry of Testing's practitioner community describes teams that "struggle with incomplete bug reports, unclear reproduction steps, and disagreements about bug severity" as a routine tax on delivery. Every ambiguous ticket is a round-trip: the developer asks, the reporter answers a day later, the developer has swapped context twice. Multiply that across a sprint and a QA team's throughput quietly halves.
The 6-field structure
1. Title — searchable + specific
A good title is the search query your future self would type to find this bug six months from now. It should name the feature, the symptom, and the condition. Bad: "checkout broken." Good: "Stripe Checkout fails with 'card_declined' on prepaid cards over $50." Atlassian recommends a title that reads like [Area] Component fails to do X after Y — for example, "[Browser] Profile picture fails to load after edit" — so it's instantly scannable in a triage queue.
2. Steps to reproduce — numbered, from a known state
1. Sign in as a free-tier user
2. Open https://app.example.com/projects/new
3. Type "Test" in the Name field
4. Click Save
→ Actual: button spins forever, no project created
→ Expected: project is created, redirect to /projects/<id>Numbered steps. Starting state explicit. Click targets unambiguous. Expected vs actual stated inline at the failure point. Mozilla's guidance is to give minimized, easy-to-follow steps and to include any special setup the bug requires — and, critically, to check that the steps reproduce in a fresh profile and on more than one machine before filing, so you're reporting a genuine defect and not a corrupted local state. The same discipline applies to any app: reproduce it in an incognito window with no extensions before you claim it's a bug in the product.
3. Expected vs actual
The two-line "→ Actual / → Expected" pattern above is the most underrated bug-report convention. It forces you to articulate the intent of the feature, not just the symptom. Google's engineers make the same point from the maintainer's side: expected-behavior information "helps you decide which API is actually broken, where it's broken, and what the expected behavior is." Stating expected behavior explicitly also heads off the most tedious triage outcome — the "this is actually working as designed" disagreement — before it becomes a week of ticket ping-pong.
4. Environment
- Browser + version (Chrome 137, Firefox 128, Safari 18.4)
- OS + version
- App version — commit SHA if available, release tag otherwise
- Device (desktop / mobile / tablet, viewport size for responsive bugs)
- Network conditions (only if relevant)
Atlassian is blunt about why this matters: "some bugs may only be reproducible in specific environments," so documenting the environment lets a developer recreate the exact conditions and isolate the cause. If your capture tool records these automatically — and it should — you can skip filling this section by hand entirely.
5. Evidence — screenshot, video, or session replay
- Screenshot for static visual bugs (alignment, contrast, typo). Crop or annotate the region of interest.
- Video / GIF for state changes you can narrate.
- Session replay for race conditions, intermittent failures, or "it works on my machine."
6. Severity signal — and why it isn't priority
State your perceived severity: "blocks production checkout" versus "minor cosmetic on the settings page." But be precise about what you're claiming. Severity is technical impact — how badly the software is broken. Priority is urgency — the order the team will fix things in. Atlassian's guidance is to rate both, because "not all [bugs] are equally important": a defect that interrupts a crucial function has to be fixed immediately, unlike a small graphical correction. A crash on a rarely used admin screen can be high severity yet low priority; a wrong price on your pricing page can be low severity yet top priority. The triager owns the final priority — but they need your severity signal to set it well.
Bad vs good: the same bug, rewritten
Nothing teaches the six fields faster than watching a useless report become a great one. Here is a real-shaped bug as it usually arrives:
Title: checkout is broken
Hey, checkout doesn't work for some users. It was fine
yesterday. Can someone look? Might be a caching thing.
Thanks!Every failure mode is here. The title isn't searchable. There are no steps, no environment, no expected-vs-actual, no evidence. "Some users" is unactionable. "Might be a caching thing" is speculation presented as a lead — exactly what Tatham warns against. A developer can do nothing with this except reply "need more info," and now you've lost a day. Now the same bug, filed properly:
Title: Checkout fails with 'card_declined' on prepaid
cards over $50 (Chrome 137, prod build 8f2a1c)
Steps to reproduce (fresh incognito window):
1. Add any item over $50 to cart
2. Go to /checkout
3. Pay with a prepaid Visa (test: 4000 0000 0000 0341)
4. Click "Pay now"
Expected: order confirms, redirect to /order/<id>
Actual: spinner hangs 8s, then "card_declined" toast.
Order IS created in Stripe but not in our DB.
Environment: Chrome 137 / macOS 15.3 / build 8f2a1c
Reproducibility: 5/5 attempts, two different machines
Severity: HIGH — blocks all prepaid-card checkouts
Evidence:
- Console: `Uncaught (in promise) 402` at checkout.ts:210
- Network: POST /api/charge → 402, but Stripe webhook
fired `payment_intent.succeeded` 300ms later (race)
- Session replay attached (see the 8s hang at 0:12)The second report is a work order. It names the exact condition (prepaid cards over $50), proves reproducibility (5/5, two machines — the check Mozilla asks for), pins the environment to a build SHA, and hands the developer the smoking gun: a 402 racing a late webhook. Nobody has to ask a follow-up question. That is the entire difference between a bug that ships a fix this afternoon and one that rots in the backlog.
Why reproducibility is the #1 factor
If you optimize one thing, optimize reproducibility. Google's testing team frames the maintainer's reality precisely: bugs that "make fast progress tend to be quick to reproduce with a clear expected behavior," while unreproducible bugs stall because they usually stem from nondeterminism — timing, ordering, uninitialized state, concurrency — that a developer can't recreate on demand. Ministry of Testing makes the consequence explicit: whether a bug can be reproduced consistently is "a key factor affecting whether it gets fixed," and an intermittent bug can be prioritized "so low that it never gets fixed" at all.
This is why the humble reproduction rate — "5/5 attempts" versus "happened once" — carries so much weight. It tells the developer whether they're chasing a deterministic defect or a heisenbug, and it sets their expectation for how much instrumentation they'll need. When you can't reproduce a bug, don't stay silent: Ministry of Testing's advice is to report irreproducible failures anyway, but "with extra care" — label the bug clearly as irreproducible, give a detailed account of exactly what happened, and attach every scrap of evidence (screen recording, console logs, API calls) you captured at the moment it occurred. A single replay of a one-time failure is often the only thing that lets an engineer form a hypothesis at all.
How to capture evidence that actually helps
Evidence is where reports either become self-sufficient or stay dependent on you. Aim to attach — or, far better, autocapture — three layers:
- Console. The errors and warnings from the failed session, with stack traces. A single uncaught exception often points straight at the line.
- Network. A HAR or request log for the relevant calls: status codes, payloads, headers, timing. Most "the button did nothing" bugs are a 4xx or 5xx hiding under a silent catch.
- State + replay. A DOM session replay for the failure window, plus any relevant client state (local storage, cookies, store snapshot). This is what turns "it hung for 8 seconds" into a scrubbable timeline you can inspect frame by frame.
The trap with manual evidence is that reporters capture the wrong slice — a screenshot of the error toast, but not the network 402 that caused it. Autocapture removes that judgment call by recording everything around the failure, which is exactly the moat that tools built on rrweb provide.
| Feature | Weak report | Strong report |
|---|---|---|
| Title | "checkout broken" | Feature + symptom + condition |
| Steps to reproduce | Missing or vague | Numbered, from a known state |
| Expected vs actual | — | ✓ |
| Environment | "my browser" | Browser, OS, build SHA |
| Reproducibility rate | Unstated | "5/5, two machines" |
| Evidence | One screenshot | Console + network + replay |
| Severity signal | — | ✓ |
| Developer's first action | Ask for more info | Start the fix |
Caveats: when more reporting is worse
The six-field discipline is a floor, not a ceiling — and pushed too far it has real failure modes worth naming honestly.
- Over-reporting. Not every observation deserves a ticket. Filing a bug for something you haven't tried to reproduce, or logging five near-identical cosmetic nits as separate P3s, buries the signal your team needs. If you can't reproduce it and have no evidence, that's a note in a channel, not a formal defect.
- Duplicate triage cost. Every large tracker fills with duplicates, and triaging them isn't free. Before filing, search existing issues — Atlassian's own bug-triage guidance treats de-duplication as a first-class triage step. A good duplicate that adds a fresh repro path or environment is useful; a lazy one just adds queue noise.
- Template rigidity. A six-field template is a prompt, not a bureaucratic gate. Don't block someone from reporting a real crash because they didn't fill the "device" field. The goal is a report a developer can act on, and Atlassian's own summary of a good report is that it's "brief but complete" — complete enough to reproduce, brief enough to read.
- Privacy. Rich capture is powerful and dangerous: replays and network logs can contain passwords, tokens, and PII. Never solve reproduction by asking a reporter to DM their credentials. Redact before the data leaves the browser.
How BugMojo makes a complete report the default
Everything above is a discipline you can adopt with a plain text template today. What tooling changes is who has to remember it. When a teammate hits a bug in your app with the BugMojo browser extension (Chrome, Firefox, or Edge) installed, they don't fill out a form — they highlight the broken element, type one sentence, and submit. BugMojo attaches:
- The recent window of DOM mutations as an rrweb session replay
- Every console message during that window
- Every network request — status, payload, headers — with PII redacted client-side, before the data ever leaves the browser
- Browser, OS, viewport, page URL, and build context
Because capture is automatic, the environment section fills itself and the evidence is the right slice by construction — the very failure modes that make manual reports bounce. BugMojo also runs no-setup Quick Capture for one-off bugs, and its MCP integration lets an AI coding agent read the same replay-backed report a human triager would, so a bug can be assigned to a person or an agent from the same ticket.
Next steps
- Adopt the six fields. Copy the title / steps / expected-vs-actual / environment / evidence / severity structure into your team's template in Linear, Jira, or GitHub Issues. Make it the default.
- Autocapture the environment and logs. Whether it's BugMojo, another rrweb-based tool, or a homegrown extension, removing the manual capture step is the single biggest workflow upgrade a QA team can make.
- Measure the round-trip. Pick a sprint, require the six fields on every bug, and track ticket round-trip time before and after. The reduction in "need more info" replies tells the rest of the story.
BugMojo captures DOM replay, console, and network in a single click — with PII redacted in the browser. Install free in under 30 seconds.
Install the extensionFrequently asked questions
Frequently asked questions
Sources
- Bug Writing Guidelines — Mozilla / MDN (updated 2025)
- How to write a good bug report (Jira bug report template) — Atlassian (2025)
- Bug reports & the art of the bug report — Ministry of Testing (2024)
- Minimizing Unreproducible Bugs — Google Testing Blog (2014)
- How to Report Bugs Effectively — Simon Tatham (PuTTY) (1999, updated 2025)
- rrweb — open-source session replay — rrweb-io (2025)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

