feat(server-utils): Capture errors thrown in Mastra - #24374
Open
mydea wants to merge 1 commit into
Open
Conversation
Contributor
size-limit report 📦
|
mydea
added this pull request to stack #24369
September 14, 2026 12:41
When a Mastra tool or model operation throws, the exporter only reflected it on
the span (error status + stack-less `error.type` from Mastra's serialized
`errorInfo`); the error never became a Sentry issue.
Mastra runs each operation inside `executeWithContext({ span, fn })`, and when
`fn` rejects that channel's `error` event carries the real `Error` — stack and
all. The integration now subscribes to it and captures the error with
`captureException` (mechanism `auto.ai.mastra`, handled: false), associated with
the exporter's span for that operation so it lands on the right trace.
`captureException` dedupes on the error instance, so an error re-thrown through
outer operations is captured once. Capturing needs no async-context binding, so
it rides the attach-only path alongside the exporter registration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mydea
force-pushed
the
feat/mastra-error-capture
branch
from
September 14, 2026 12:43
30f6e44 to
a2593c6
Compare
RulaKhaled
added a commit
that referenced
this pull request
Sep 14, 2026
Matches #24374: mechanism `auto.ai.flue` with `handled: false`, and the capture runs under the operation's own span so the issue lands on the right trace. Unlike Mastra, the rebuild from `errorInfo` stays. Mastra's `errorInfo` is `{ name, message }` with no stack, which is why it has to reach for the channel's real `Error`; Flue's carries the original stack, so there is nothing to gain from a second seam that might not fire on every path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mydea
marked this pull request as ready for review
September 14, 2026 14:26
mydea
requested review from
a team,
JPeer264,
RulaKhaled and
isaacs
and removed request for
a team
September 14, 2026 14:26
RulaKhaled
added a commit
that referenced
this pull request
Sep 14, 2026
Matches #24374: mechanism `auto.ai.flue` with `handled: false`, and the capture runs under the operation's own span so the issue lands on the right trace. Unlike Mastra, the rebuild from `errorInfo` stays. Mastra's `errorInfo` is `{ name, message }` with no stack, which is why it has to reach for the channel's real `Error`; Flue's carries the original stack, so there is nothing to gain from a second seam that might not fire on every path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
logaretm
reviewed
Sep 14, 2026
| const span = id ? getSentrySpanForMastraId(id) : undefined; | ||
| // `captureException` dedupes on the error instance, so the same error re-thrown through outer | ||
| // `executeWithContext` calls is captured only once. | ||
| const capture = (): string => captureException(error, { mechanism: { type: 'auto.ai.mastra', handled: false } }); |
Member
There was a problem hiding this comment.
h: I think we should avoid capturing errors on the channel subscriber like we do with other subscriptions.
The tracing channel tells us that the promise/call rejected but doesn't guarantee it was handled by try/catch or .then/.catch or not.
The error subscriber is only good for setting span status, not capturing errors, which should still happen on global unhandled hooks.
| function captureMastraError(error: unknown, params: unknown): void { | ||
| const id = isObjectLike(params) ? mastraSpanId(params.span) : undefined; | ||
| const span = id ? getSentrySpanForMastraId(id) : undefined; | ||
| // `captureException` dedupes on the error instance, so the same error re-thrown through outer |
Member
There was a problem hiding this comment.
m: clanker raised this, Mastra seems to wrap errors with new MastraError so they would probably escape the dedup-by-identity logic we have.
I raised another point about not capturing at all which would make this pointless anyways.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #24368.
When a Mastra tool or model operation throws, the exporter only reflected it on the span (error status + a stack-less
error.type, from Mastra's serializederrorInfo) — the error never became a Sentry issue.Mastra runs each operation's work inside
executeWithContext({ span, fn })(the same seam #24368 uses for nesting). Whenfnrejects, that channel'serrorevent carries the realError, stack and all. The integration now subscribes to it and captures the error withcaptureException(mechanismauto.ai.mastra,handled: false), insidewithActiveSpanof the exporter's span for that operation so the issue lands on the right trace.captureExceptiondedupes on the error instance, so an error re-thrown through outer operations is captured only once.Capturing needs no async-context binding, so it rides the attach-only path next to the exporter registration (not gated behind
waitForTracingChannelBinding).Root cause: the exporter receives
errorInfo({ name, message }), not the thrownError, so it can only set span status — there's no stack to build a real issue from. The actualErroris only observable at the throw site, whichexecuteWithContext's channel error surfaces.Verified in the node integration suite (Node 22, ESM + CJS): a thrown tool error is captured as an issue with
type: Error, the real message, a non-empty stack, and mechanismauto.ai.mastra/handled: false. Thenode-mastrae2e error test now also asserts the captured issue (not just the errored span), and the corresponding TODO is dropped.🤖 Generated with Claude Code