Skip to content

Sentry tracing breaks third-party diagnostics_channel TracingChannel stream instrumentation #20291

@pocketcolin

Description

@pocketcolin

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

10.48.0

Framework Version

n/a

Link to Sentry event

No response

Reproduction Example/SDK Setup

Environment

@sentry/node: 10.48.0
@anthropic-ai/sdk: 0.87.0
braintrust: 3.8.0
Node.js: v22.13.0
Express 5.2.1

Summary

Enabling Sentry tracing (tracesSampleRate or tracesSampler) prevents Braintrust's wrapAnthropic() from tracing streamed Anthropic API responses. Non-streaming responses trace fine. Disabling Sentry tracing entirely resolves the issue. No amount of httpIntegration configuration (spans: false, ignoreOutgoingRequests) helps. It seems like the conflict is at the OpenTelemetry context propagation layer.

Root cause analysis (warning: written by Claude)

Braintrust's wrapAnthropic() proxies messages.create() and wraps it in a diagnostics_channel TracingChannel.tracePromise() call. When messages.create({ stream: true }) resolves, braintrust's asyncEnd handler patches [Symbol.asyncIterator] on the resolved Stream object to collect chunks. When iteration completes, the span is logged.

When Sentry tracing is enabled, two things interfere with this:

  • SentryHttpInstrumentation (@sentry/node-core/build/cjs/integrations/http/SentryHttpInstrumentation.js:159) calls api.context.bind(api.context.active(), response) on the HTTP response stream as soon as the response event fires.
  • @opentelemetry/instrumentation-http (build/src/http.js:274) does the same — api.context.bind(api.context.active(), response).

api.context.bind() on an EventEmitter (AbstractAsyncHooksContextManager.js:57-80) patches the response object's listener methods (on, once, addListener, prependListener, removeListener, off, removeAllListeners), wrapping every callback in a context-propagating wrapper and storing mappings via Symbol('OtListeners').

This mutation of the HTTP response stream — which underlies the Anthropic SDK's Stream async iterable — interferes with braintrust's ability to patch and observe the [Symbol.asyncIterator] lifecycle on the asyncEnd event. The span is created but never receives output or gets .end() called, so it silently drops.

Non-streaming works because the response is fully buffered before braintrust processes it — no async iterator involved.

What I've tried (none work)

Sentry.httpIntegration({ spans: false })
Sentry.httpIntegration({ ignoreOutgoingRequests: (url) => url.includes('api.anthropic.com') })

These configure the HTTP integration but don't prevent the OpenTelemetry context manager from binding to response streams globally.

Steps to Reproduce

Minimal reproduction using an Express server with both Sentry and Braintrust:

import * as Sentry from '@sentry/node'
Sentry.init({
    dsn: process.env.SENTRY_DSN,
    tracesSampleRate: 1, // removing this fixes the issue
})

import { initLogger, wrapAnthropic } from 'braintrust'
import Anthropic from '@anthropic-ai/sdk'
import express from 'express'

initLogger({ projectId: '...', apiKey: '...' })
const app = express()

app.get('/stream', async (req, res) => {
    const anthropic = wrapAnthropic(new Anthropic())
    const response = await anthropic.messages.stream({
        model: 'claude-haiku-4-5',
        messages: [{ role: 'user', content: 'Say hello' }],
        max_tokens: 100,
    })
    for await (const event of response) {
        if ('delta' in event && 'text' in event.delta) res.write(`data: ${event.delta.text}\n\n`)
    }
    await response.finalMessage()
    res.end()
})

app.listen(3210)

Save that to a test.ts file and run it with npx tsx ./test.ts.

  • With tracesSampleRate: 1: The stream completes successfully (the client receives all chunks), but no trace appears in Braintrust.
  • Without tracesSampleRate: The trace appears in Braintrust as expected.
  • Without Sentry entirely: The trace appears in Braintrust as expected.

Expected Result

Sentry's OpenTelemetry context binding on HTTP response streams should not break third-party [Symbol.asyncIterator] patching on those streams. Libraries using diagnostics_channel TracingChannel to instrument streaming responses should continue to function when Sentry tracing is enabled.

Actual Result

Stream completes, but no trace appears in Braintrust.

Additional Context

No response

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugNode.jsjavascriptPull requests that update javascript code
    No fields configured for issues without a type.

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions