DEV Community

Cover image for Crash triage that ships fixes: read every Sentry issue like a release engineer
Dave Kurian
Dave Kurian

Posted on Originally published at otf-kit.dev

Crash triage that ships fixes: read every Sentry issue like a release engineer

Every AI-built app reaches the same Tuesday morning. The Sentry inbox grew overnight, the loudest crash is something obscure on an old Android build, and nobody knows which issue deserves the next hour. Most teams triage by volume: they open the issue with the biggest event count and start reading stack traces. That feels productive and usually wastes the morning, because event volume measures noise, not harm. A handled warning firing ten thousand times on a deprecated build looks enormous and matters not at all.

A release engineer reads the inbox differently. They ask which users are blocked, which release introduced the fault, and whether the fix can ride the next build. This post is that workflow, tuned for React Native apps built fast with AI assistance: more generated code, thinner institutional memory, and a higher chance that the person triaging never wrote the crashing line. You do not need deep Sentry expertise. You need a queue, release tags you trust, and the discipline to write down a fix order.

The whole routine takes about thirty minutes per release cycle once the wiring is in place. It rests on three Sentry primitives — issue states, release tagging, and event grouping — plus one team habit. Everything below assumes Sentry is already capturing crashes in your app; if that wiring is missing, start with the Sentry release checklist first, then come back and learn to work the inbox it produces.

Start with the review list, not the loudest crash

Sentry's Issues page splits unresolved work into tabs, and most builders ignore them and scroll the default list. That is the first mistake. The tab labeled For Review — the is:unresolved is:for_review filter — holds new issues and regressions that nobody has looked at yet. That tab is the triage queue. Everything else on the page is either already owned by someone or deliberately parked. Open the review tab first, every single time, and treat clearing it as the definition of a finished triage session.

Inside that queue, sort by users affected, not by event count. One crash hitting four hundred users on the current release beats four thousand repeats of a handled warning on a three-month-old build, every time. Event counts are dominated by loops: a retry storm, a polling screen, a background task that throws on every wake. User counts measure blocked humans. When two issues compete, the one touching the current release and a core flow — signup, checkout, the home feed — wins regardless of the numbers beside it.

Then keep the queue honest. Every reviewed issue leaves the tab the same session: resolved if it is fixed, archived if it is parked, assigned if someone owns it. A review list with two hundred stale entries is not a queue, it is wallpaper, and teams stop reading wallpaper. Save the useful filters as saved searches — current release plus for-review is the one you will open daily — so triage starts with one click instead of five minutes of filter construction.

Tag every release so triage knows what changed

Triage without release tags is archaeology. An issue that says "first seen three weeks ago" tells you nothing; an issue that says "first seen in release 2.14.0" points at a specific set of changes. Every event your app sends must carry the release, the build identifier, and the environment that produced it. In a React Native app using the Sentry SDK, that looks like this:

import * as Sentry from "@sentry/react-native";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  release: `my-app@${APP_VERSION}+${BUILD_NUMBER}`,
  dist: BUILD_NUMBER,
  environment: __DEV__ ? "development" : "production",
});
Enter fullscreen mode Exit fullscreen mode

Three details matter here. First, the release string combines the user-facing version with the build number, because app stores let the same version number ship multiple binaries and over-the-air updates add more variants on top. Second, the dist field separates those variants, so a crash introduced by one over-the-air bundle does not smear across the whole version. Third, the environment flag keeps development crashes — which you generate constantly while building with an AI agent — out of the production signal entirely.

AI-built apps need this discipline more than hand-built ones, not less. An agent can regenerate half a navigation stack between releases, which means "what changed" is a bigger question and the answer lives in the release tag, not in anyone's memory. When the release tag is trustworthy, the triage question "did we cause this or did the platform?" usually answers itself in one glance at the first-seen release. Connecting your repository to Sentry additionally enables suspect-commit suggestions on issues, which narrows that glance to specific changes.

Read the grouping before the stack trace

Sentry folds events with matching fingerprints into a single issue, which is what makes an inbox of a million events readable at all. But grouping is a heuristic, and heuristics misfire in both directions. Before trusting an issue's shape, spend one minute checking whether it is what it claims to be. Unrelated crashes merged into one issue mean a single fix will never close it — you will ship, celebrate, and watch it reopen. One bug split across three issues means three people may fix the same root cause in parallel, or worse, each fix looks ineffective because two-thirds of the events keep flowing.

The symptoms are easy to spot once you look. A merged issue has stack traces that diverge wildly between events, or an error value that changes meaning halfway down the event list. Split issues share an identical error value and failing module but different fingerprints, often because the crash surfaces through slightly different call paths. Sentry lets you merge and split issues from the issue page, so correcting the shape is cheap — the expensive part is fixing the wrong shape for a week.

AI-generated codebases produce split issues more often than hand-written ones. Agents love duplicating helpers across screens with small variations, so the same flawed pattern crashes through five slightly different call paths and becomes five issues. Before fixing any crash in generated code, search the issue list for the same error value. If siblings exist, fix the shared pattern once, link the issues, and resolve them together. This single habit probably saves more hours than any other paragraph in this post.

Turn repeat offenders into a weekly fix order

Triage output should be a written list, not a feeling. Once per cycle — weekly for most teams, per release for fast ones — convert the reviewed queue into an ordered fix list with exactly four tiers. Tier one: crashes blocking core flows on the current release. Tier two: regressions, because a returned bug means a previous fix failed and that demands attention. Tier three: high-user-count handled errors that degrade but do not block. Tier four is not a tier at all: everything else gets archived with a reason or stays assigned to its owner.

Cap the list at what fits in one cycle. Five fixes that ship beat twenty that linger, and an overflowing fix list teaches the team that triage output is aspirational fiction. Assign each item to exactly one person with a ship vehicle — the next release, the next over-the-air update, a hotfix. Risky fixes deserve a flag to roll back behind rather than a prayer, because the triage workflow only works if shipping the fix feels safe. An unassigned issue with no vehicle is just a wish. Review last cycle's list at the start of each session: shipped, carried over with a reason, or re-triaged because the data changed.

The other half of a sustainable fix order is shrinking the inbox at the source. Known noise — network-flake errors your retry logic already handles, third-party SDK chatter you cannot act on — should be filtered in the SDK before it ever becomes an issue. The beforeSend hook is the right tool:

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  beforeSend(event) {
    if (__DEV__) return null;
    const value = event.exception?.values?.[0]?.value ?? "";
    if (value.includes("Network request failed")) return null;
    return event;
  },
});
Enter fullscreen mode Exit fullscreen mode

Filter conservatively: drop only what you have proven unactionable, and revisit the filters quarterly because today's noise can be tomorrow's outage signature. Fewer junk events means the review list reflects real harm, the user counts stay meaningful, and the fix order writes itself from the top of the queue downward. Teams that skip this step end up triaging the same retry storm every Tuesday for a year.

Archive deliberately and watch the regressed tab

Archiving is a decision, not surrender, provided it carries a reason. Old-release crashes nobody can reproduce, third-party SDK faults with an upstream report filed, device-specific ghosts with eleven affected users on a deprecated OS — these are legitimate archives. Write the reason on the issue when you archive it, because future-you will otherwise reopen it in a panic and re-triage from zero. A reasoned archive is documentation; a silent one is a trap.

Two tabs then become your early-warning system. The Regressed tab — resolved issues that came back — tells you a fix did not hold or a new release reintroduced an old fault, and it deserves a look after every single release. The Escalating tab — archived issues whose volume returned — tells you a parked problem woke up. Check both as part of the release ritual, before users report what the tabs already know.

And when a regressed spike traces to exactly one bad bundle, invert the order: roll back first, triage second. There is no triage insight worth more than unshipping the cause, and a tested rollback plan turns that decision from a scary incident into a routine step. Triage tells you what broke; the rollback button tells you how fast you can unbreak it. Keep both sharp.

Sources

Top comments (0)