test(node): Add Mastra e2e test app - #24362
Conversation
| "scripts": { | ||
| "build": "mastra build && rm -rf .mastra/output/node_modules/@sentry", | ||
| "start": "mastra start --custom-args=\"--import=./instrument.mjs\"", | ||
| "dev": "mastra dev --custom-args=\"--import=./instrument.mjs\"", |
There was a problem hiding this comment.
Sentry preload import path is wrong
High Severity
--import=./instrument.mjs cannot resolve instrument.mjs. Node resolves that relative specifier from the process cwd, but the file only exists at src/mastra/public/instrument.mjs. Mastra copies public/ into .mastra/output/public/ as static assets, not the output root, and mastra dev does not copy it at all. Both start commands fail before Sentry.init() runs.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit babf76f. Configure here.
There was a problem hiding this comment.
this is wrong, this works indeed
size-limit report 📦
|
Mastra serves its API and custom `registerApiRoute`s on an internal Hono
server, so Sentry's HTTP instrumentation named the incoming `http.server`
span from the raw URL (high cardinality, no `http.route`). `@sentry/hono`
can't help — it instruments a Hono app you own, and Mastra never exposes
its app instance.
The Mastra integration now injects a route-naming Hono middleware into the
Mastra server config at construction time (via the orchestrion `Mastra`
constructor channel, before Mastra reads `config.server`). After the handler
runs it resolves the matched route pattern from the Hono context and upgrades
the root `http.server` span to `${method} ${route}` with `http.route` set and
name source `route` — reusing `setHttpServerSpanRouteAttribute`. Works for
built-in API routes (`POST /api/agents/:agentId/generate`) and custom routes
(`GET /echo/:id`) alike, with zero app code. Disable with
`mastraIntegration({ instrumentServerRoutes: false })`.
Adds a parametrized route + `http.server` route-naming coverage to the
node-mastra e2e app, and unit tests for the injection and route resolution.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaYGdTstWVZKU9X4NjtYV1
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 06e931c. Configure here.
This reverts commit 06e931c.
…ed (#24368) Stacked on #24362. The Mastra exporter builds the full `gen_ai` span tree (agent/model/tool) with rich attributes, but those spans are inactive, so work Mastra does _inside_ an operation — a `dataloader.load` in a tool, the outgoing model `fetch` — didn't nest under them and floated up to the request root. Mastra runs every operation's work inside its own `executeWithContext({ span, fn })` helper (the stable `@mastra/core/observability/context-storage` export). Orchestrion now wraps that helper and, via `bindSpanToChannelStore`, makes the exporter's Sentry span for that Mastra span id active for the call. It activates an already-created span — it never opens or ends one, so there are no duplicate spans and the exporter keeps owning the lifecycle; the exporter publishes an id→span registry for the lookup. This mirrors Mastra's own intended nesting into Sentry's async context rather than reconstructing it. Result: the model-provider `http.client` request nests under `chat`, and tool-internal orchestrion spans (e.g. dataloader `cache.get`) under `execute_tool`. The `node-mastra` e2e app drops its manual `Sentry.startSpan` workaround in `count_items` accordingly. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


Summary
Adds an e2e test app (
node-mastra) exercising the Sentry Mastra integration end-to-end.Covers agent spans, tool inputs/outputs, error capturing, conversation id, a non-Mastra orchestrion package (
dataloader) driven through a tool, and custom HTTP routes (static + parametrized) — run against bothmastra start(prod bundle) andmastra dev.How to run a Mastra app with Sentry (documented by this app)
Sentry.init()via--import, not an in-bundle import (mastra start --custom-args="--import=./instrument.mjs"); an in-bundleimport './instrument'gets tree-shaken away.instrument.mjsinsrc/mastra/public/so Mastra copies it into.mastra/output/automatically.@sentry/*copy: the build deletes.mastra/output/node_modules/@sentryso the output resolves upward to the app's copy — the preload and bundle then share one SDK, so bundle-sideSentry.startSpan()works.bundler.externals: ['dataloader']) — a copy inlined into the bundle is never transformed.POST /api/agents/:id/generatewith nestedmemory: { thread, resource }(top-levelthreadId/resourceIdroute to the deprecatedgenerateLegacypath).TLDR:
mastra start --custom-args="--import=./instrument.mjs"Findings from building this
executesignature. In this Mastra versionToolExecuteFunctionis(inputData, context) => ...— the validated input is the first positional argument, not{ context }. Tools written asexecute({ context })silently threw on every call, which showed up as: nodataloadercache.getspans, emptygen_ai.tool.call.result, and the model retrying the "failing" tool repeatedly. Fixed by usingexecute(inputData); tool arguments and results are then captured.gen_ai.*span isauto.ai.mastra; there are noauto.vercelai.*spans.metadata.threadIdtogen_ai.conversation.id, no app code needed.Currently documented shortcomings
TODOs in
src/mastra/index.ts:error.type) but not sent as a Sentry error; the exporter leavescaptureExceptionto the app.dataloader(and other orchestrion span-openers) need an active parent span — Mastra runs tools with inactive spans, so thecount_itemstool opens its own viaSentry.startSpan().