feat(server-utils): Ensure Mastra tool/model spans are correctly nested - #24368
Merged
Merged
Conversation
mydea
force-pushed
the
feat/mastra-active-spans
branch
from
September 14, 2026 09:50
44e5a06 to
db51de2
Compare
mydea
added this pull request to stack #24369
September 14, 2026 09:51
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
Contributor
size-limit report 📦
|
mydea
force-pushed
the
feat/mastra-active-spans
branch
from
September 14, 2026 09:58
db51de2 to
114b61d
Compare
mydea
marked this pull request as ready for review
September 14, 2026 10:55
mydea
requested review from
a team,
JPeer264,
RulaKhaled and
isaacs
and removed request for
a team
September 14, 2026 10:55
chargome
self-requested a review
September 14, 2026 12:35
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
force-pushed
the
feat/mastra-active-spans
branch
from
September 14, 2026 12:41
114b61d to
1a93c22
Compare
chargome
reviewed
Sep 14, 2026
RulaKhaled
approved these changes
Sep 14, 2026
RulaKhaled
left a comment
Collaborator
There was a problem hiding this comment.
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 }); |
Collaborator
There was a problem hiding this comment.
does stream() get the same nesting? might be worth a test
Member
Author
There was a problem hiding this comment.
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
approved these changes
Sep 15, 2026
chargome
left a comment
Member
There was a problem hiding this comment.
Thanks for adding the changes!
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 #24362.
The Mastra exporter builds the full
gen_aispan tree (agent/model/tool) with rich attributes, but those spans are inactive, so work Mastra does inside an operation — adataloader.loadin a tool, the outgoing modelfetch— 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-storageexport). Orchestrion now wraps that helper and, viabindSpanToChannelStore, 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.clientrequest nests underchat, and tool-internal orchestrion spans (e.g. dataloadercache.get) underexecute_tool. Thenode-mastrae2e app drops its manualSentry.startSpanworkaround incount_itemsaccordingly.