Automatic grouping
One bug, one issue, no matter how many times it happens
Every exception is fingerprinted from its class, message, and backtrace, so the same bug reported a thousand times shows up as one issue, not a thousand.
How the fingerprint is actually built
Grouping runs entirely in a background job, never in the ingestion request itself, so a burst of traffic never makes the client wait on it. The fingerprint is a SHA-256 hash of the exception class, a normalized version of the message, and a normalized version of the backtrace, and normalization is deliberately simple rather than a full templating engine: good enough to group the common cases without pretending to solve every one.
In-app backtrace frames (the ones your own client tags as yours, not a gem's or the framework's) are preferred over framework frames for grouping, since your own code is what actually changes between two occurrences of the same logical bug. Line numbers are dropped entirely, since they shift on every unrelated refactor, and gem version segments are stripped out of file paths, so bumping a gem doesn't fragment an issue that hasn't actually changed.
The message itself gets normalized too: hex object ids, UUIDs, and bare numeric ids are all replaced with placeholders before hashing, since those are unique per occurrence, not per bug. "Couldn't find Invoice with id=8821" and "Couldn't find Invoice with id=9004" hash identically. Grouping is capped to the first 10 in-app frames, both to keep very deep stacks stable and to keep hashing cheap at ingestion volume.
Every issue is one row, however many times it's actually happened.
Regressions
Mark an issue resolved and it stays that way until the exact same fingerprint is reported again. When that happens, it flips itself back to unresolved and stamps a regressed_at timestamp, with no fresh report needed from anyone to notice: the next occurrence does it automatically, the moment it's grouped.
This only applies to a resolved issue. An ignored issue reported again stays ignored; ignoring is a "stop telling me about this specific fingerprint" decision, not a temporary snooze, so it doesn't get treated as a regression the way resolving does.
Each occurrence keeps its own timestamp, environment, and backtrace, grouped under one issue.
Safe under concurrent load
A brand-new fingerprint reported by a sudden burst of concurrent requests doesn't create duplicate issues. Grouping relies on a unique database index across project and fingerprint, not an application-level check-then-create: if two workers both try to insert the same new fingerprint at once, the database itself rejects the second insert, and that worker retries into the update path instead, landing on the exact same issue the first worker created.