The report is sitting in your tracker. The user saw it happen — you believe them — but on your machine everything works, and you've already burned an hour proving it. Now what?
Most teams have exactly one move here: label it "cannot reproduce" and close it. That move quietly costs you twice. The bug stays in production, where your next users will find it. And the reporter learns that filing bugs is pointless, so the next report — the one with the detail that would have cracked this one — never gets filed.
Diagnosing why a bug reproduces for them and not for you is its own discipline — the difference is always somewhere in state, timing, environment, or data. This post is about the other half of the job: the workflow. What you actually do with the ticket, today, when reproduction has failed.
Replace "closed" with a real decision
An unreproducible report has three honest outcomes, and "closed" isn't one of them:
Fixed anyway — the evidence pointed at a cause you could correct without ever seeing the bug live.
Parked with instrumentation — you couldn't crack it, so you set a trap for the next occurrence and moved on.
Watching — the report is one data point; you're deliberately waiting for a second.
Every step below feeds one of those three. The goal of triage isn't reproduction — it's getting the ticket to a decision you can defend.
Step 1: Squeeze the report you have
Before touching a browser, extract everything the report actually contains:
Cross-reference the timestamp. Line the report time up against your server logs and your deploy history. "It broke at 14:02" next to "we deployed at 13:58" has closed more mysteries than any debugger.
Search for siblings. Same page, same error text, same endpoint — in your tracker and in your error monitoring. A report that looks unique is often the third of its kind under three different titles.
Read the symptom backwards. If the button "did nothing", enumerate what could swallow a click: a disabled state, an early return, an unhandled rejection. Each hypothesis implies a condition the user could have been in. You're not reproducing yet — you're building a shortlist.
Step 2: Spend one timeboxed hour reproducing like them
Reproduction attempts fail because developers reproduce as themselves: fresh session, fast network, admin account, big screen. Spend one hour — and only one — deliberately being the reporter instead. Their browser and device class, their viewport, a throttled connection, an account with their role and their volume of data, their sequence of actions rather than the sensible one.
If the hour produces nothing, stop. Unbounded reproduction hunts are how a medium bug eats a sprint. The playbook continues either way.
Step 3: Ask the reporter one question, not five
A questionnaire gets abandoned; a single specific question gets answered. Pick the one whose answer splits your shortlist in half — "does it still happen after a refresh?", "does it happen on the same record every time?" — and send only that.
One more rule: ask for facts, never for labor. "Which browser?" is a fact users can't reliably give you and tooling can capture automatically; asking for it spends reporter goodwill on something a script tag should have done.
Step 4: Park it properly
Parking is not closing. A properly parked ticket has three things:
The evidence attached — everything from Step 1, plus your eliminated hypotheses, so nobody re-runs the same hour next month.
A trap set — add the log line, the breadcrumb, the assertion that will fire when it happens again. If your reporting tool captures console output, a well-placed
console.errorin the suspect path turns the next report into the diagnosis.A recurrence trigger — the ticket reopens when a sibling report arrives, rather than relying on someone remembering it exists.
Step 5: Treat the second report as the break in the case
Unreproducible bugs recur — that's the one reliable thing about them. And the second report changes the game: one report is an anecdote, two are a comparison, five are a dataset. Ask what the occurrences share (browser? role? one specific record? time of day?) and what they don't. The shared variables are your cause; the rest is noise.
This is why duplicate reports should pile onto a single task instead of being closed as duplicates — on exactly this class of bug, the pile is the analysis. It's also a place an AI assistant genuinely helps: connected to your reports through BugDump's MCP server, Claude Code or Cursor can page through every report on a task and surface the common thread faster than a human scrubbing five session replays.
Keep the reporter in the loop
Two sentences, at two moments: when you park it ("we couldn't pin this down yet — we've added monitoring and kept your report open") and when it's fixed ("your report from March cracked this — thank you"). The first preserves the relationship; the second is the cheapest reporting-culture investment that exists. Reporters who see their reports matter file better ones.
Fix the pipeline, not just the bug
Everything this playbook works around — missing environment, missing sequence, missing errors — existed in the reporter's browser at the moment of failure. It just wasn't captured. That's the durable fix: when a report is filed through BugDump, it arrives with the console buffer (errors flagged), recent network requests (failures highlighted), browser, OS, device, viewport, page URL, and a scrubbable session replay of what the user actually did. Add one identify call and you know which account to inspect instead of guessing at one like it.
With that pipeline in place, most of this playbook stops being necessary: Step 1 is pre-done, Step 2 starts from facts, Step 3's question is usually already answered. "Cannot reproduce" doesn't disappear entirely — but it stops being a resolution and becomes what it should have been all along: a rare, temporary state between a report and a fix.