Skip to content

feat(server-utils): Ensure Mastra tool/model spans are correctly nested - #24368

Merged
mydea merged 2 commits into
developfrom
feat/mastra-active-spans
Sep 15, 2026
Merged

mydea merged 2 commits into
developfrom
feat/mastra-active-spans

Conversation

@mydea

@mydea mydea commented Sep 14, 2026

Copy link
Copy Markdown
Member

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.

@mydea
mydea force-pushed the feat/mastra-active-spans branch from 44e5a06 to db51de2 Compare September 14, 2026 09:50
@mydea
mydea added this pull request to stack #24369 September 14, 2026 09:51

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit db51de2. Configure here.

Comment thread packages/server-utils/src/integrations/mastra.ts
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.96 kB - -
@sentry/browser - with treeshaking flags 27.26 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.15 kB - -
@sentry/browser (incl. Tracing) 50.51 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.52 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.5 kB - -
@sentry/browser (incl. Tracing, Replay) 90.07 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.16 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.77 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.73 kB - -
@sentry/browser (incl. Feedback) 46.46 kB - -
@sentry/browser (incl. sendFeedback) 34.01 kB - -
@sentry/browser (incl. FeedbackAsync) 39.12 kB - -
@sentry/browser (incl. Metrics) 29.98 kB - -
@sentry/browser (incl. Logs) 30.24 kB - -
@sentry/browser (incl. Metrics & Logs) 30.91 kB - -
@sentry/react 30.72 kB - -
@sentry/react (incl. Tracing) 52.81 kB - -
@sentry/vue 36.2 kB - -
@sentry/vue (incl. Tracing) 52.76 kB - -
@sentry/svelte 28.98 kB - -
CDN Bundle 30.7 kB - -
CDN Bundle (incl. Tracing) 51.01 kB - -
CDN Bundle (incl. Logs, Metrics) 32.98 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 52.99 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.67 kB - -
CDN Bundle (incl. Tracing, Replay) 88.56 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.53 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.64 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.63 kB - -
CDN Bundle - uncompressed 90.83 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.33 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.41 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.29 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 226.82 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 271.9 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 277.85 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.6 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.54 kB - -
@sentry/nextjs (client) 55.13 kB - -
@sentry/sveltekit (client) 50.93 kB - -
@sentry/core/server 37.13 kB - -
@sentry/core/browser 13.66 kB - -
@sentry/node 130.66 kB +0.19% +240 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.88 kB +0.07% +50 B 🔺
@sentry/node - without tracing 89.69 kB +0.09% +80 B 🔺
@sentry/node - without channel injection 109.56 kB +0.2% +211 B 🔺
@sentry/aws-serverless 97.92 kB +0.09% +80 B 🔺
@sentry/cloudflare (withSentry) - minified 203.36 kB - -
@sentry/cloudflare (withSentry) 506.44 kB - -

View base workflow run

@mydea
mydea force-pushed the feat/mastra-active-spans branch from db51de2 to 114b61d Compare September 14, 2026 09:58
@mydea
mydea marked this pull request as ready for review September 14, 2026 10:55
@mydea
mydea requested review from a team as code owners September 14, 2026 10:55
@mydea
mydea requested review from a team, JPeer264, RulaKhaled and isaacs and removed request for a team September 14, 2026 10:55
@mydea mydea changed the title feat(server-utils): Nest Mastra tool/model work under the exporter spans feat(server-utils): Ensure Mastra tool/model spans are correctly nested Sep 14, 2026
@chargome
chargome self-requested a review September 14, 2026 12:35
Base automatically changed from fn/mastra-e2e-test-app to develop September 14, 2026 12:41
The Mastra exporter builds the full gen_ai span tree (agent/model/tool) with
rich attributes, but those spans were inactive, so work Mastra does inside an
operation — a `dataloader.load` in a tool, an outgoing model `fetch` — did not
nest under them.

Mastra runs every operation's work inside its own `executeWithContext({ span,
fn })` helper (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 — activating
an existing span, never opening or ending one (the exporter still owns the
lifecycle). The exporter publishes an id→span registry for the lookup.

Result: the model-provider `http.client` request nests under the `chat` span and
tool-internal orchestrion spans (e.g. dataloader `cache.get`) under the
`execute_tool` span. 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>
@mydea
mydea force-pushed the feat/mastra-active-spans branch from 114b61d to 1a93c22 Compare September 14, 2026 12:41
Comment thread packages/server-utils/src/ai/mastra/span-registry.ts Outdated

@RulaKhaled RulaKhaled left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me! worth it to test how stream behaves as well

});

await Sentry.startSpan({ op: 'function', name: 'mastra-test' }, async () => {
await mastra.getAgent('counter_agent').generate('Count the letters in apple and banana.', { maxSteps: 3 });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does stream() get the same nesting? might be worth a test

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a test for this as well!

…multiple instances

Address review feedback on the active-span bridge:

- Bound the span registry with an `LRUMap` (shared cap `MAX_TRACKED_MASTRA_SPANS`,
  moved to constants) instead of a plain `Map`. It's keyed by globally-unique
  Mastra span id, so it already works across multiple `Mastra` instances/exporters;
  the LRU just keeps the process-wide table bounded if an unregister is ever missed.
- Add an integration test driving the agent via `stream()` against an SSE mock:
  the model fetch nests under `chat` and tool work under `execute_tool` on the
  streaming path too.
- Add an integration test with two concurrent `Mastra` instances in one trace,
  asserting each instance's `dataloader.load` nests under its own `execute_tool`
  span (no registry cross-talk).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chargome chargome left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the changes!

@mydea
mydea merged commit f97122f into develop Sep 15, 2026
261 of 262 checks passed
@mydea
mydea deleted the feat/mastra-active-spans branch September 15, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants