Error tracking
Every exception, grouped once, owned by someone.
Unhandled folds each exception into an issue with its stack trace, the trace that raised it and the users it reached. Route it to its owner, link a ticket, and hear about it again only if it comes back.
- One issue per bug
- Grouped on the type and your own frames
- Regressions
- A resolved issue reopens when it returns
- Ownership
- Rules assign by path, route or service
- Linear and GitHub
- Closing the ticket resolves the issue
A thousand events, one row
Each exception gets a fingerprint from its type and the top three frames of your own code, so an ID or a timestamp in the message never splits a bug in two.
- Frames from node_modules and the runtime are left out, so a dependency upgrade keeps your issues intact
- A redeploy to a new directory, or new hashes in chunk names, groups with the old build
- Similar issues sit on the issue page with a one-click merge
The occurrence worth reading opens first
The inspector picks the occurrence with source context, a trace and an identified user. Step to older or newer ones with the keyboard.
- Stack frames with the surrounding source, your own code marked apart from libraries
- The full trace of the request or job, and the logs and spans before the throw
- Other exceptions recorded in the same trace, each linked to its issue
Readable stacks from minified bundles
Upload source maps from CI with the Unhandled CLI. Frames are mapped back when the exception arrives, before it is grouped, so browser errors group on your real function names.
- Maps match on the Sentry debug id first, then on the release and the closest file path
- Uploads authenticate with a project deploy key, never the DSN shipped in your bundle
- Up to 1 GB of maps per project, replaced in place when you upload a file again
export UNHANDLED_API_KEY=dk_live_…
export UNHANDLED_DSN=https://…@ingest.unhandled.net/…
npm run build
unhandled sourcemaps upload ./dist --release 2.4.0Route each issue to its owner
Ownership rules map a file path, a route or a service to a person or a team. Turn on auto-assign and the first matching rule assigns each new issue.
- Globs work like CODEOWNERS: * stays in one segment and ** crosses them
- Archive until escalating, until more users are hit, for a while, or forever
- Resolve, assign, archive and step to the next issue without touching the mouse
Told once, in the right channel
New and regressed issues are batched for a minute and sent as one message. Exception rules decide which Slack, Discord or webhook channel hears about each service and environment.
- The most specific rule wins, and a rule can mute a channel type for noisy services
- Members with exception summaries on get the same batch by email
- Assignees hear about comments and status changes on their issues
Report the errors you handle
The AdonisJS agent reports what your exception handler sees, with the signed-in user attached. For an error you catch and recover from, report it yourself.
- Attach a user, tags you can filter on, and extra data
- Each error object is reported once, even if you rethrow it
- Any OpenTelemetry SDK works too: recordException on a span is enough
import { Unhandled } from '@unhandled/agent-adonisjs'
try {
await PaymentGateway.charge(order)
} catch (error) {
Unhandled.captureException(error, {
user: { id: order.customerId },
tags: { area: 'checkout', provider: 'stripe' },
extra: { orderId: order.id },
})
await order.markPaymentFailed()
}Works with
Where your team already works
- LinearCreate or link a ticket. Completing it resolves the issue.
- GitHubOpen or link an issue. Closing it resolves the issue here.
- Slack and DiscordBatched messages for new and regressed issues.
- WebhooksSigned JSON for every batch, to wire into anything.
- MCP serverAgents read, triage, merge and link issues from your editor.
- Copy for AIThe stack, context and trace as Markdown for a coding agent.
Questions
Try it on your own stack.
Every feature is on every plan, priced by the telemetry you send.