Reproducing a bug from a Sentry issue
An alert lands in your team channel: TypeError: Cannot read properties of undefined (reading 'currency'), 412 events, 88 users affected. You open the issue, look at the stack trace, run the same page locally, and everything works.
The issue is not lying. You are just missing four or five details that the 88 users had and you do not. Sentry recorded most of them. The job is to read the event carefully enough to rebuild the conditions.
Short version
- Read the event, not just the title. The title is a grouped label; the event has the evidence.
- Tags tell you who and where: release, browser, URL, environment, user.
- Breadcrumbs tell you what happened before: clicks, navigation, network requests, console output.
- The release tag lets you check out the exact code that crashed, which removes most false starts.
- Reproduce on the same release first. Only then move forward to
main. - If three honest attempts fail, write down what you tried and add the instrumentation you wish you had.
An issue is not one bug
Sentry groups similar events into an issue using a fingerprint, usually built from the error type and the top stack frames. That grouping is helpful and occasionally wrong.
Before you spend an hour on a repro, check whether the issue is really one problem:
- Open the All Events list and look at three or four events from different users.
- Compare their tags. If some are on release
2.9.0and others on3.1.0, or some on Safari and some on Chrome, you may have two different causes wearing the same label. - Look at the event graph. A flat line that starts on a specific day points at a deploy. A steady trickle points at an edge case in the data.
Pick one representative event and work from that. Trying to reproduce "the issue" in general is how people end up chasing three bugs at once.
Step 1: read the tags
Tags are the searchable key-value pairs attached to every event. They answer "under what conditions did this happen?"
The ones that matter most:
| Tag | Why it matters |
|---|---|
release | The exact build. Lets you check out the same code. |
environment | Production, staging, or preview. A staging-only error often means bad test data. |
url | The page. Look at the path parameters, not just the route. |
browser / browser.name | Version-specific bugs are common, especially in Safari. |
os | Rarer, but decisive for file, clipboard, and date bugs. |
user | The account. Role and plan usually explain "works for me". |
transaction | The route or component the error belongs to. |
| Custom tags | Feature flags, tenant ID, plan tier — whatever your team adds. |
The habit that pays off most is the tag distribution panel. Sentry shows the percentage split for each tag across the issue. If browser.name: Safari is 100%, you have your first real clue and can stop reading. If plan: free is 96%, the bug lives in a code path that paying accounts skip.
If your app does not set custom tags yet, this is the moment to add them. A tenant, plan, and flags tag turns future triage from an hour into two minutes.
Step 2: read the breadcrumbs backwards
Breadcrumbs are the timeline of what happened in the session before the error. Sentry records them automatically: navigation, clicks, network requests, console messages, and anything you add yourself.
A real breadcrumb trail looks like this:
14:32:01 navigation /projects -> /projects/882/invoices
14:32:03 ui.click button#export-invoices
14:32:03 fetch GET /api/v2/invoices?project=882 200 412ms
14:32:04 console warn "pricing block missing for legacy plan"
14:32:04 ui.click button.currency-toggle
14:32:04 error TypeError: Cannot read properties of undefined
Read it from the bottom up. Six lines tell you nearly everything:
- The error follows a click on the currency toggle.
- One second earlier the app warned that a pricing block was missing.
- The invoice request succeeded, so this is not a network failure.
- The user came from the projects list, so state may have been carried over.
Now you have a hypothesis you can test: the currency toggle assumes a pricing block that legacy plans do not have.
Three things to look for every time:
- The last network request before the error. Its URL, status, and duration. A 200 that returns an unexpected shape causes more bugs than a 500.
- The last user action. Clicks and input changes are recorded with a CSS selector, which points you at the component.
- Warnings you have been ignoring. Console breadcrumbs often contain the explanation your team wrote months ago and stopped reading.
One limit worth knowing: breadcrumbs are capped, usually at 100 entries. A long session drops the early ones. If the trail starts mid-flow, that is why.
Step 3: match the release
This step removes more wasted time than any other.
The release tag names the build the user was on. If it says a1b2c3d and you are testing against today's main, you may be testing code that already changed. Check out the same commit:
git checkout a1b2c3d
npm ci
npm run dev
Two extra checks:
- Compare against the current release. Sentry shows whether the issue is still occurring on newer releases. If the last event was on
2.9.0and you are on3.1.0, it may already be fixed. - Look at the "first seen" release. If the issue first appeared in
2.8.4, the cause is in the diff between2.8.3and2.8.4. That is often a small enough range to read.
If the trace is unreadable — full of names like t.n (main.4f2a.js:1:88213) — your source maps are not being uploaded for that release. Fix that before anything else. Guessing at minified frames is not debugging.
Step 4: rebuild the conditions
Now turn the evidence into steps. Work through these in order and stop as soon as it fails.
- Check out the release commit and run the app locally.
- Recreate the account state. Match the role, plan, and workspace from the
usertag. Create a test account such as[email protected]with the same plan tier rather than using a real customer's login. - Recreate the data. This is the step people skip. If the breadcrumb showed
/api/v2/invoices?project=882, look at what project 882 actually contains — a legacy plan, an unusual currency, 600 line items, a name with an apostrophe. - Match the browser. If the tag distribution points at one browser and version, use it.
- Set the same feature flags. From your custom tags or your flag provider's dashboard.
- Follow the breadcrumbs as literally as you can. Same entry page, same clicks, same order. Order matters more than people expect, because it determines what state is already in memory.
- Watch the console and network as you go. You are looking for the same warning and the same response shape, not only the same crash.
If it still passes, one shortcut often works: skip the UI and call the failing code directly with the data from the event. If the error object includes the response body or the component props, feed those values into the function from the top stack frame. A failing unit test written from real production data is a better repro than a manual click path, and it stays as a regression test.
When you cannot reproduce it
Three honest attempts is a fair limit. After that, change the goal from "reproduce it" to "make the next event tell me more."
- Add context at the failure point. Attach the values you wish you had:
Sentry.setContext('invoice', { planTier, currency, lineItemCount }). The next event answers the question for you. - Add breadcrumbs by hand around the suspicious flow, so the timeline shows internal state and not just clicks.
- Add a tag for the condition you suspect, such as
has_pricing_block: false. Then check the tag distribution after a day. - Check session replay if your team has it enabled. Watching the session usually settles it in a minute.
- Ask the user. For a small number of affected accounts, a support message with two specific questions beats a week of guessing.
Sentry events are also one-directional: they tell you what the machine saw, not what the person expected. When the gap between those two matters, a human-filed report helps, and tools such as Crosscheck capture the screenshot, console log, network requests, and environment details from the page so the report arrives with the same evidence an automatic event would have.
What to write in the ticket
Once you have a repro, record it so nobody has to redo the work:
Repro (confirmed on release a1b2c3d, Chrome 141, staging)
Account: [email protected] (plan: Legacy, role: Editor)
Data: Project 882, invoice in EUR, no pricing block on the plan
1. Sign in and open https://staging.example.com/projects/882/invoices
2. Click "Export invoices" and wait for the list to load
3. Click the currency toggle
Expected: currency switches to USD
Actual: page goes blank, TypeError in console (Sentry issue FE-2291)
Link the ticket to the Sentry issue in both directions. When a fix ships, the issue can be marked as resolved in the next release, and Sentry will reopen it automatically if it comes back — which is the cheapest regression test you will ever get.
Frequently asked questions
Why can I never reproduce production errors locally? Usually data and account state, not code. Production has old records, unusual currencies, large collections, and users with roles you do not test. Match those before you doubt the report.
What if the stack trace is all minified names? Source maps are not reaching your error tracker for that release. Fix the upload first; a minified trace is not worth reading.
How many events should I look at before deciding on a cause? At least three, from different users. One event can be a fluke. Three with the same tag pattern is a real signal.
Should I close an issue I cannot reproduce? Do not close it silently. Add the context or tags you need, note what you tried, and set it to review after a few days of new events.
Are breadcrumbs safe to read? They can contain personal data from URLs and form fields. Treat them like any other production data, and scrub sensitive fields in your SDK configuration.




