A user reports that the page breaks. You follow their steps exactly. Everything works. You close the ticket as cannot reproduce, and three weeks later four more people report the same thing.
"Works on my machine" gets treated as a punchline, but it is a precise and useful statement: the bug depends on some difference between your environment and theirs. That reframes an unanswerable question into a tractable one. Not "why is this bug so weird" but "what is different between these two machines?"
There are not that many candidate differences. In browser applications, essentially all of them fall into eight categories, and each leaves a recognisable fingerprint.
1. State
The single most common cause. You reproduce from a clean load. They have been in the tab for two hours.
Long-lived single-page applications accumulate: stale entries in a client-side cache, a websocket that reconnected with a different session, a store that was mutated by a code path they visited an hour ago, an access token that refreshed halfway through. Your fresh page load has none of this history.
Fingerprint: it works after a refresh. If a reporter says "refreshing fixes it," you are almost certainly here, and the reproduction you need is not their steps — it is their steps after twenty minutes of unrelated use.
How to catch it: the ordered list of what the user actually did in the session, not what they thought was relevant. This is what an action log gives you and a steps-to-reproduce field never will, because the relevant step is the one they did not think to mention.
2. Timing
Everything on your machine is fast. Local API, warm cache, fast disk. On a phone on a train, a request that takes 40ms for you takes 4 seconds — and a race condition that never loses on your machine loses reliably on theirs.
Fingerprint: intermittent. "It happens maybe one in five times." Intermittent almost always means timing, and it means the two operations racing are close enough in duration that ordinary variance flips the winner.
How to catch it: network timings. Look at what was in flight simultaneously and how long each request took. The classic shape is two requests started together where the code assumed the first would finish first.
How to reproduce it: throttle. DevTools network throttling and CPU throttling turn a great many "cannot reproduce" bugs into "reproduces every time." This is the highest-value five minutes in this entire post.
3. Environment
Browser, version, OS, and — most underrated — viewport size.
You test at 1440px on a desktop Chrome. They are at 390px on iOS Safari, or at 1280px with the browser zoomed to 125%, which is not the same as 1024px in ways that break layouts. Safari in particular still diverges on date parsing, on backdrop filters, on scroll behaviour, and on how it treats storage in private browsing.
Fingerprint: consistent for the reporter, never for you. Reproduces 100% of the time on their machine.
How to catch it: browser, OS, and viewport recorded automatically with the report. This is the category with the highest ratio of "trivially fixed once known" to "impossible to guess" — which is exactly why asking a user for their browser version is such a bad use of a support round-trip.
4. Identity and permissions
You are logged in as an admin, on your own account, in your own organisation. They are a read-only member of a team of forty, on a plan with a feature flag off.
Role-dependent bugs are systematically under-caught because developers almost never browse their own product as the least-privileged user.
Fingerprint: reproduces for one class of user and not another. Often reported by several people at once who share a role, which makes it look like a sudden regression when it has been broken for months.
How to catch it: attach the user's role, plan, and flag state to the report. Any decent SDK lets you attach your own context — in BugDump that is identify() for who they are and setContext() for arbitrary state — and role and plan are the two fields worth attaching before any others.
5. Data
Their account has a record with an emoji in the name, or a null where you assumed a string, or 4,000 rows where your fixtures have 12, or a currency with no decimal places, or a date in 1970 because an import went wrong.
Fingerprint: reproduces on one specific record and nowhere else. The reporter will often say "only this one project does it," which is the entire diagnosis handed to you.
How to catch it: the record ID and the failing API response. A response body with the offending value in it collapses this category instantly — which is a large part of why response bodies are so useful, and why they need to be handled carefully, since the same field that contains the malformed date also contains everything else about the user.
6. Extensions and injected scripts
Ad blockers block your analytics endpoint, and your code did not handle that request failing. Password managers inject DOM into your form and break event handling. Translation extensions rewrite text nodes and shatter React's reconciliation. Corporate proxies rewrite responses.
Fingerprint: nobody can reproduce it, including other users, and the console shows a request that failed with no status — or a DOM error pointing at an element you never rendered.
How to catch it: failed requests with no response are the tell. So is a session replay showing DOM that your application does not produce.
7. The network itself
Captive portals, corporate TLS interception, DNS failures, a CDN edge serving a stale bundle to one region, an in-flight deploy where the HTML is new and the chunk it references is already gone.
Fingerprint: geographically or temporally clustered. Several reports in a short window, or all from one country.
How to catch it: the timestamps and the failing asset URL. A ChunkLoadError in the console is this category almost by definition, and it means your deploy strategy is deleting old chunks before old tabs are done with them.
8. Time
Timezones, daylight-saving transitions, month boundaries, locale date formats, and the machine whose clock is eleven minutes off — which is enough to make a token appear expired on arrival.
Fingerprint: reproduces only at certain hours, or only for users in certain regions, or only near the end of a month.
How to catch it: the reporter's timezone offset and local time alongside the server timestamp. Cheap to record, and it decides this category on sight.
The method
Given a report you cannot reproduce, the useful move is not to try harder. It is to sort it into one of the eight above, and each has a question that identifies it fast:
Does refreshing fix it? → state
Is it intermittent? → timing
Is it 100% consistent for them? → environment
Do they share a role or plan with other reporters? → identity
Is it one specific record? → data
Did a request fail with no status? → extensions or network
Is it clustered in time or geography? → network
Only at certain hours? → time
Notice that every one of these questions is answered by data, not by conversation. Which is the actual reason "cannot reproduce" is so common: the evidence that would sort the bug into its category was available in the user's browser at the moment it broke, and nobody captured it.
Capture the difference, not the description
The reason this is worth systematising is that unreproducible bugs are not rare edge cases. They are the ones that stay open. Reproducible bugs get fixed the same week. The backlog is made of the other kind, and the other kind is almost entirely a data problem.
A report that arrives with the browser, OS, viewport, timezone, user role, session history, console, and network log does not need a category to be guessed at — the difference between their machine and yours is written down in the report. That is what BugDump captures automatically from a single script tag, so the eight questions above get answered before anyone has to ask one of them.
The rarest and most valuable thing in this list is the second reporter. One unreproducible report is a mystery; three of them, with three different environments attached, is a controlled experiment — the variables they share are the cause, and the ones they do not are noise. Grouping duplicate reports onto a single task is not tidying up. On exactly the bugs you cannot reproduce, it is the diagnosis.