Issues
How Unhandled groups exceptions into issues, the issue lifecycle, triage actions, regressions, retention and notifications.
An issue is one bug as Unhandled sees it: every occurrence of the same exception, grouped together so you triage it once instead of once per event. Issues live at the project level. Status, priority and assignees are shared across environments, while counts and charts follow the environment, services and time range you have selected.
Open Issues in the project sidebar. The list shows the issues seen in the current environment, and opens on For review.
Where exceptions come from
Unhandled builds issues from exceptions recorded on spans (OpenTelemetry exception span events) and from log records that carry exception attributes. The AdonisJS agent and the browser SDK report both automatically; with any other OpenTelemetry SDK, recordException is enough.
A grouping job runs every minute. It reads exceptions by the time Unhandled received them, not by the timestamp the SDK set. An exception that arrives late, from an SDK's offline queue, an ingest backlog or a host with a wrong clock, still lands in its issue and can still open or regress it. The job waits until ingest has stored everything received up to 30 seconds ago, so it never skips an exception that is still on its way.
Before storage, Unhandled scrubs secrets from messages, stack traces, frame variables, tags and extra data: passwords, tokens, cookies, card numbers and keys. Admins can turn this off, or name more fields to scrub and fields never to scrub, under Exceptions in Settings, General for the project.
A crash loop stores the first 100 events per issue per minute in full, up to 2,000 per project per minute. The rest are counted but not stored, so event totals and trends stay exact.
Grouping
Each exception gets a fingerprint:
- With a stack trace, the fingerprint is the exception type plus the top 3 in-app frames, each as function name and the frame's module or file name without its directory. A frame is in-app unless its file is under
node_modules, starts withnode:, or isnative. The same code deployed to a different directory, or rebuilt with new hashes in its chunk names, groups together. Frames served from a URL count by function only. Generated names, such as Java lambdas and proxies, Clojure functions and Ruby blocks, lose their per-build ids, and a recursion counts once. - Without in-app frames, the fingerprint is the type plus the message, with variable parts replaced by placeholders: URLs, email addresses, UUIDs, ISO dates, IPv4 addresses, long hex strings and numbers.
To group by your own rule, send a fingerprint with the exception. Sentry SDKs set it with scope.setFingerprint, and other SDKs set the unhandled.exception.fingerprint attribute to a JSON array of strings. {{ default }} in the array stands for the fingerprint Unhandled computes.
When an exception has causes, the cause types are added to the grouping key. DatabaseError caused by a timeout and the same DatabaseError caused by a constraint violation become separate issues.
The issue title is <type>: <message> from the earliest occurrence, or just the type when there is no message.
Grouping versions
The fingerprint rules above are grouping version 2, the default for new projects. Exceptions in the project's settings shows the version in use. On a self-hosted server, node ace grouping:upgrade --to 2 moves every project to version 2, or one project with --project <id>. For a week afterwards Unhandled computes both versions, so an exception whose new fingerprint has no issue yet still joins the issue its old fingerprint belongs to, and nothing splits or alerts as new.
Status and priority
An issue is Open, Resolved or Archived.
| Action | Effect |
|---|---|
| Resolve | Marks the issue resolved. A new occurrence after that reopens it as a regression. |
| Reopen | Moves a resolved or archived issue back to open. |
| Archive | Hides the issue until a condition is met, for a while, or forever. See Archives. |
Priority is Low, Medium, High or Critical. Unhandled sets it when it creates the issue: Critical for a fatal exception, High for an unhandled exception or one in a request that answered 5xx, and Low for a handled exception. When an issue archived until escalating wakes up, its priority goes up one step.
Regressions
If a resolved issue gets an occurrence later than the moment it was resolved, it reopens, gets the Regressed flag, and fires a regression notification. Archived issues never regress: occurrences of an archived issue are counted but do not change its status.
Through the JSON API (POST /o/:org/projects/:project/api/issues/:issue/actions), you can also resolve an issue in a release with action=resolve&resolution=in_release&release=<version>, or in the next release with resolution=in_next_release. Only an occurrence from that release or a later one reopens it, and occurrences without a release never do. Releases compare as semantic versions, or by the order Unhandled first saw them.
Archives
Archive offers three kinds of archive. Archive until escalating is the default.
| Kind | Options | Ends when |
|---|---|---|
| Until something changes | Until escalating, until 10, 100 or 1,000 more events, until another user is affected, until 10 more users | Escalating: one hour holds at least 3 times the issue's average hourly rate over the week before the archive, and at least 5 events. The others: the count since the archive reaches the number, across every environment. |
| For a while | 1 hour, 1 day, 1 week, 30 days | The time runs out. A maintenance job checks every 5 minutes. |
| Permanently | Forever | Someone reopens it. |
An archived row in the list shows its condition and the progress toward it. When an archive ends, the issue goes back to open. Expiry does not post to channels and does not create in-app notifications; assignees get an email.
Linked issues
You can create or link a GitHub or Linear issue from the issue page. When the linked issue is closed in the tracker, Unhandled resolves the issue if it is open or in a timed archive. See GitHub and Linear.
Triage
From the list or the issue page you can:
- Resolve, reopen or archive.
- Change priority.
- Assign up to 20 people, and teams. Assignees must have access to the project. The assignee menu offers Assign to me, suggested people, teams and a search.
- Comment, up to 5,000 characters.
- Merge other issues into this one, up to 50 at a time. The merged issues' fingerprints, environments, assignees and tracker links move to the target, and the merged issues are deleted. Future occurrences go to the target. Similar issues on the issue page suggests candidates with a one-click merge. To undo a merge,
POST /o/:org/projects/:project/api/issues/:target/unmergewith the mergedissueIdgives it back its fingerprints. There is no button for it. - Mark it reviewed, which takes it out of For review.
- Copy for AI, which copies Markdown with the stack, context and trace to paste into a coding agent.
Select issues in the list to apply the same action to up to 100 of them at once. Anyone who can see the project can triage its issues, in the app or through the MCP server.
On the issue page, the keyboard covers the whole loop. Press ? to list the keys.
| Key | Action |
|---|---|
r | Resolve, or reopen |
e | Archive menu |
Shift+E | Archive until escalating |
a | Assignee menu |
Shift+A | Assign to me, or unassign me |
p | Priority menu |
m | Mark reviewed |
c | Comment |
j, k | Next and previous issue |
[, ] | Older and newer occurrence |
Esc | Back to the list |
Turn on Open the next issue after triage at the bottom of the issue page to work down the list without going back to it. In the list, j and k move, x selects, m marks reviewed and Enter opens.
Every change is recorded in the activity panel: creation, status changes, regressions, first sighting in a new environment, priority changes, assignments, comments, merges and tracker links.
Ownership and auto-resolve
Settings, Issue triage holds the project's triage defaults. Everyone with access can read it, and admins change it.
- Ownership rules map a File path (such as
app/services/billing/**), a Route (such asPOST /checkout/*) or a Service (such asworker-*) to a person or a team.*stays inside one path segment and**crosses segments. With Auto-assign new issues to their owner on, the first matching rule assigns each new issue. Without a match it stays unassigned. - Auto-resolve after days without events resolves open issues that stay quiet for that many days, from 1 to 365. A later event regresses them. Leave it empty to keep issues open until someone resolves them.
The issue list
The list has these columns: Issue, Culprit, Priority, Assignees, Trend, Events, Users, Age and Last seen.
- Views: tabs above the list. For review (the default), All open, Assigned to me and High volume are built in. Save the current search, status and sort as your own view, personal or shared with the project. Anyone who can see the project can delete a shared view.
- Status: Open (default), Resolved, Archived or All.
- Sort: Last seen (default), First seen, Events, Users or Trending.
- Search matches the title, type, message and culprit, and takes filters.
For review lists open issues that are new or regressed in the last 7 days and that nobody has triaged or marked reviewed. Any triage action counts as a review, and a regression puts the issue back. The sidebar shows the count next to Issues.
Search filters are field:value. Separate several values with commas, and put ! in front, or use !=, to negate.
| Filter | Values |
|---|---|
is: | for_review, new, regressed, unhandled, handled, unassigned, assigned, escalating |
priority: | low, medium, high, critical |
assignee: | me, none, an email address, or #team |
service:, release:, user:, level:, instance: | Values from the issue's occurrences |
first_release: | The release the issue was first seen in |
first_seen:, last_seen: | An age such as <24h or >7d, or a date |
For example, is:unhandled release:1.4.0 assignee:me first_seen:<24h.
The issue page
The issue page has three tabs. Overview shows counts, affected users, first and last seen, a chart, services and entry points. Occurrences lists individual events, and Users lists the identified users who hit the issue. The header shows the first and last release, each linked to its deployment. The side panel shows the tag distribution and the latest occurrences.
The occurrence inspector opens on the best documented occurrence, the one that has the most of source context, a trace and an identified user (newest first on ties), and you can switch to the latest or step to older and newer occurrences. It shows the stack trace with in-app frames marked, the causes, the trace the exception belongs to and the context recorded with it. When the occurrence has a trace, a Timeline tab lists the logs and spans of that trace before the exception. Other exceptions recorded in the same trace are listed under Other exceptions in this trace.
Notifications
New and regressed issues are batched and sent:
- To notification channels, according to the project's exception rules. See Notifications.
- By email to every project member who keeps Exception summaries on.
New and regressed issues do not appear in the in-app inbox. Assignees get an inbox entry for assignments, manual status changes and comments, except for their own actions. They also get an email for each of those, for regressions and for expired archives.
Retention
An issue with no occurrence in any environment for 90 days is deleted, unless it is archived forever or a person triaged it: assigned it, linked it, commented on it or changed its status. A later occurrence of a kept issue still regresses it.
Environments
Issues are shared across environments, but Unhandled tracks when each issue was first and last seen in every environment. The first occurrence in a new environment is added to the activity panel.