skills-legacy/sentry-span-streaming-js/SKILL.md
Migrate JavaScript SDK to Sentry span streaming (span-first trace lifecycle). Use when asked to "enable span streaming", "migrate to span streaming", "use traceLifecycle stream", "add spanStreamingIntegration", or switch from transaction-based to streamed span delivery in a JavaScript project.
npx skillsauth add getsentry/sentry-for-ai sentry-span-streaming-jsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
All Skills > Feature Setup > Span Streaming (JavaScript)
Migrate from the default transaction-based trace lifecycle (static) to span streaming (stream), where spans are sent individually as they complete instead of being batched into a transaction at the end.
This skill covers the JavaScript SDK (Browser, Node.js, Bun, Deno, Cloudflare). For Python, see Span Streaming (Python). For Dart/Flutter, see Span Streaming (Dart).
traceLifecycle, spanStreamingIntegration, or withStreamedSpan# Detect if browser, server, or both
grep -rn "from '@sentry/browser'\|from '@sentry/react'\|from '@sentry/vue'\|from '@sentry/angular'\|from '@sentry/svelte'\|from '@sentry/nextjs'\|from '@sentry/nuxt'\|from '@sentry/sveltekit'\|from '@sentry/remix'\|from '@sentry/solidstart'\|from '@sentry/astro'\|from '@sentry/react-router'" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null | head -20
grep -rn "from '@sentry/node'\|from '@sentry/bun'\|from '@sentry/deno'\|from '@sentry/cloudflare'" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null | head -20
# Find Sentry.init calls
grep -rn "Sentry\.init\|init({" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null | head -20
# Find beforeSendSpan usage
grep -rn "beforeSendSpan" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null
# Find beforeSendTransaction usage
grep -rn "beforeSendTransaction" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null
# Find ignoreSpans usage
grep -rn "ignoreSpans" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" -l 2>/dev/null
Based on detection results, classify each Sentry.init call as:
| Environment | Packages | Migration Path |
|---|---|---|
| Browser | @sentry/browser, @sentry/react, @sentry/vue, @sentry/angular, @sentry/svelte | Add spanStreamingIntegration() |
| Server | @sentry/node, @sentry/bun, @sentry/deno, @sentry/cloudflare | Add traceLifecycle: 'stream' |
| Framework (both) | @sentry/nextjs, @sentry/nuxt, @sentry/sveltekit, @sentry/remix, @sentry/astro, @sentry/solidstart, @sentry/react-router | Migrate both client and server configs separately |
Prerequisites: Sentry JavaScript SDK >=10.66.0 with tracing enabled (tracesSampleRate or tracesSampler configured).
Apply changes to each Sentry.init call. Work through each file identified above.
Add traceLifecycle: 'stream' to Sentry.init():
// Before
Sentry.init({
dsn: '...',
tracesSampleRate: 1.0,
});
// After
Sentry.init({
dsn: '...',
tracesSampleRate: 1.0,
traceLifecycle: 'stream',
});
Add Sentry.spanStreamingIntegration() to the integrations array. The integration automatically enables traceLifecycle: 'stream' — you do not need to set it manually.
// Before
Sentry.init({
dsn: '...',
integrations: [
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 1.0,
});
// After
Sentry.init({
dsn: '...',
integrations: [
Sentry.spanStreamingIntegration(),
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 1.0,
});
The order of spanStreamingIntegration() relative to other integrations does not matter.
Apply the browser migration to client config files and the server migration to server config files. Common patterns:
| Framework | Client Config | Server Config |
|---|---|---|
| Next.js | sentry.client.config.ts | sentry.server.config.ts, sentry.edge.config.ts |
| Nuxt | Client-side Sentry.init in module | Server-side Sentry.init in module |
| SvelteKit | src/hooks.client.ts | src/hooks.server.ts |
| Remix | entry.client.tsx | entry.server.tsx |
| Astro | Client-side init | Server-side init |
beforeSendSpanIf the user has a beforeSendSpan callback, it must be wrapped with Sentry.withStreamedSpan() to work in streaming mode. Without this wrapper, the SDK falls back to static mode.
The callback shape also changes:
description is now namedata is now attributesStreamedSpanJSON instead of SpanJSON// Before (static mode)
Sentry.init({
beforeSendSpan: (span) => {
if (span.description?.includes('/health')) {
span.description = '[filtered]';
}
// 'data' contains span attributes
delete span.data?.['http.request.body'];
return span;
},
});
// After (streaming mode)
Sentry.init({
beforeSendSpan: Sentry.withStreamedSpan((span) => {
if (span.name?.includes('/health')) {
span.name = '[filtered]';
}
// 'attributes' replaces 'data'
if (span.attributes) {
delete span.attributes['http.request.body'];
}
return span;
}),
});
Key differences in the callback:
| Static (SpanJSON) | Streaming (StreamedSpanJSON) |
|---|---|
| span.description | span.name |
| span.data (processed attributes) | span.attributes (raw attributes) |
| span.timestamp (end time) | span.end_timestamp |
| span.status (optional string) | span.status ('ok' or 'error') |
| span.op | span.attributes['sentry.op'] |
Returning null from beforeSendSpan does not drop the span — it is ignored and a warning is logged.
setTag(s) to setAttribute(s)In streaming mode, tags no longer apply to streamed spans — only attributes do. Wherever the user sets tags via Sentry.setTag / Sentry.setTags or scope.setTag / scope.setTags (e.g. via withScope(), getCurrentScope(), getGlobalScope()), add an equivalent setAttribute / setAttributes call with the same key/value pairs so the data still reaches spans.
Tags continue to work for errors, so keep the existing setTag(s) calls and add the attribute calls alongside them — do not replace one with the other.
Find existing usage:
grep -rn "\.setTag\b\|\.setTags\b\|setTag(\|setTags(" --include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" --include="*.mjs" 2>/dev/null
Apply the migration. Most setTag(s) calls run in a code path that also produces spans, so default to adding the attribute equivalent unless the call is clearly scoped to error reporting only.
// Before
Sentry.setTag('feature', 'checkout');
Sentry.setTags({ tier: 'premium', region: 'eu' });
// After — keep the tags, add matching attributes
Sentry.setTag('feature', 'checkout');
Sentry.setAttribute('feature', 'checkout');
Sentry.setTags({ tier: 'premium', region: 'eu' });
Sentry.setAttributes({ tier: 'premium', region: 'eu' });
The same applies to scope-based calls:
// Before
Sentry.withScope((scope) => {
scope.setTag('job', 'sync-orders');
// ...
});
Sentry.getCurrentScope().setTag('job', 'sync-orders')
// After
Sentry.withScope((scope) => {
scope.setTag('job', 'sync-orders');
scope.setAttribute('job', 'sync-orders');
// ...
});
Sentry.getCurrentScope().setTag('job', 'sync-orders')
Sentry.getCurrentScope().setAttribute('job', 'sync-orders')
setAttribute(s) is available from SDK >=10.66.0 — confirm the prerequisite version before applying this step.
beforeSendTransactionbeforeSendTransaction has no effect in streaming mode. Spans are sent individually, not batched into transactions.
// Before
Sentry.init({
beforeSendTransaction: (event) => {
// This entire callback is ignored in streaming mode
if (event.transaction === '/health') {
return null;
}
return event;
},
});
Migration paths depending on what beforeSendTransaction was used for:
| Use Case | Streaming Replacement |
|---|---|
| Drop spans by name/route | Use ignoreSpans option |
| Modify span data before send | Use beforeSendSpan with withStreamedSpan |
| Filter by transaction name | Use ignoreSpans with string/RegExp pattern |
| Add tags/context to transaction | Use beforeSendSpan with withStreamedSpan |
Remove the beforeSendTransaction option from Sentry.init() after migrating its logic.
ignoreSpans (Optional)ignoreSpans works in both static and streaming modes, but the filter is evaluated at different points in the span lifecycle:
In both modes, a match prevents the span from being recorded or sent. Because matching can run as early as span start (streaming), only the span name and attributes set when the span begins are guaranteed to be available — do not rely on attributes added later in the span's lifetime.
Sentry.init({
traceLifecycle: 'stream',
ignoreSpans: [
// String match against span name
'/health',
'/ready',
// RegExp match against span name
/^OPTIONS /,
// Object filter — all conditions must match
{
op: 'middleware.handle',
name: /^corsMiddleware/,
},
// Filter by attributes (string = substring match, RegExp for patterns)
{
op: 'http.server',
attributes: {
'http.route': /^\/internal\//,
},
},
],
});
Filter object properties:
| Property | Type | Matches Against |
|---|---|---|
| name | string \| RegExp | Span name (description) |
| op | string \| RegExp | Span operation |
| attributes | Record<string, string \| RegExp \| number \| boolean \| Array> | Span attributes |
When multiple properties are specified in a filter object, all must match for the span to be ignored.
When using span streaming in the browser, use the v2 profiling options — not the legacy profilesSampleRate. The legacy option is deprecated and does not integrate with the span streaming lifecycle.
Add browserProfilingIntegration() and configure the two v2 options:
// Before (legacy profiling — do NOT use with span streaming)
Sentry.init({
integrations: [
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 1.0,
profilesSampleRate: 0.5, // deprecated
});
// After (v2 profiling with span streaming)
Sentry.init({
integrations: [
Sentry.spanStreamingIntegration(),
Sentry.browserTracingIntegration(),
Sentry.browserProfilingIntegration(),
],
tracesSampleRate: 1.0,
profileSessionSampleRate: 1.0,
profileLifecycle: 'trace',
});
v2 profiling options:
| Option | Type | Description |
|---|---|---|
| profileSessionSampleRate | number (0–1) | Percentage of user sessions that have profiling enabled. Default: 0 (disabled). |
| profileLifecycle | 'trace' \| 'manual' | 'trace': profiler runs automatically while sampled root spans exist. 'manual': start/stop profiler explicitly via Sentry.uiProfiler.startProfiler() / stopProfiler(). Default: 'manual'. |
profileLifecycle: 'trace' requires tracing to be enabled (tracesSampleRate or tracesSampler). The profiler starts when a root span begins and stops when no sampled root spans remain. Profile chunks are sent independently every 60 seconds or when the last root span ends.
Do not mix legacy and v2 options. If profilesSampleRate is set, profileSessionSampleRate has no effect and the SDK logs a warning.
After applying changes, verify the migration works correctly.
# TypeScript check
npx tsc --noEmit 2>&1 | head -30
# Build
npm run build 2>&1 | tail -20
Instruct the user to verify in their browser devtools or server logs:
application/vnd.sentry.items.span.v2+json rather than transaction envelopesbeforeSendSpan callback is likely missing the withStreamedSpan wrapper| Symptom | Cause | Fix |
|---|---|---|
| SDK falls back to static mode | beforeSendSpan not wrapped with withStreamedSpan | Wrap callback: Sentry.withStreamedSpan(callback) |
| beforeSendTransaction not called | Expected in streaming mode | Migrate logic to beforeSendSpan or ignoreSpans |
| Spans still arrive as transactions | traceLifecycle not set or integration missing | Server: add traceLifecycle: 'stream'; Browser: add spanStreamingIntegration() |
| Type errors on span.description | StreamedSpanJSON uses name not description | Change span.description to span.name in callback |
| Type errors on span.data | StreamedSpanJSON uses attributes not data | Change span.data to span.attributes in callback |
| profileSessionSampleRate has no effect | Legacy profilesSampleRate is also set | Remove profilesSampleRate and use only profileSessionSampleRate + profileLifecycle |
| Tags missing from spans | Tags do not apply to streamed spans | Add matching setAttribute(s) calls alongside existing setTag(s) calls |
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: '__DSN__',
tracesSampleRate: 1.0,
traceLifecycle: 'stream',
});
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: '__DSN__',
integrations: [
Sentry.spanStreamingIntegration(),
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 1.0,
});
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: '__DSN__',
integrations: [
Sentry.spanStreamingIntegration(),
Sentry.browserTracingIntegration(),
Sentry.browserProfilingIntegration(),
],
tracesSampleRate: 1.0,
profileSessionSampleRate: 1.0,
profileLifecycle: 'trace',
});
>=10.66.0traceLifecycle: 'stream'spanStreamingIntegration()beforeSendSpan callbacks wrapped with Sentry.withStreamedSpan()beforeSendSpan callbacks updated: description -> name, data -> attributessetTag(s) / scope.setTag(s) calls paired with matching setAttribute(s) callsbeforeSendTransaction logic migrated to beforeSendSpan or ignoreSpansbeforeSendTransaction removed from configprofilesSampleRate with profileSessionSampleRate + profileLifecyclebrowserProfilingIntegration() to integrationsdevelopment
Migrate Python SDK to Sentry span streaming (span-first trace lifecycle). Use when asked to "enable span streaming", "migrate to span streaming", "use trace_lifecycle stream", or switch from transaction-based to streamed span delivery in a Python project.
development
Keep Sentry SDKs up to date. Use when asked to upgrade the Sentry SDK across major versions, migrate SDK versions, or fix deprecated APIs.
testing
Full Sentry Snapshots setup for Apple/Cocoa projects. Use when asked to "setup SnapshotPreviews", "setup Apple snapshot testing", "upload Apple snapshots to Sentry", "setup Apple snapshot GitHub Actions", or "setup Apple selective snapshot testing".
development
Setup Sentry AI Agent Monitoring in any project. Use when asked to monitor LLM calls, track AI agents, track conversations, or instrument OpenAI/Anthropic/Vercel AI/LangChain/Google GenAI/Pydantic AI/Laravel AI. Detects installed AI SDKs and configures appropriate integrations.