Skip to content

fix(node): Use context-scoped suppression for LangChain + AI provider span deduplication#22366

Draft
nicohrubec wants to merge 3 commits into
developfrom
feat/langchain-scoped-ai-suppression
Draft

fix(node): Use context-scoped suppression for LangChain + AI provider span deduplication#22366
nicohrubec wants to merge 3 commits into
developfrom
feat/langchain-scoped-ai-suppression

Conversation

@nicohrubec

@nicohrubec nicohrubec commented Jul 17, 2026

Copy link
Copy Markdown
Member

When both LangChain and an AI provider integration (OpenAI, Anthropic, Google GenAI) are active, a single LangChain invoke() call produces duplicate gen_ai.chat spans — one from LangChain's callback handler and one from the underlying provider instrumentation.

The previous fix globally disabled provider instrumentations when LangChain was detected, but this broke direct provider SDK calls unrelated to LangChain.

This PR sets a suppression flag on the current scope during LangChain invoke/stream/batch execution. Provider instrumentations check this flag and skip span creation if set.

Closes #19687

… span deduplication

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

@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 2 potential issues.

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 c96914f. Configure here.

Comment thread packages/node/src/integrations/tracing/langchain/instrumentation.ts
Comment thread packages/core/src/tracing/ai/suppression.ts
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 27.74 kB - -
@sentry/browser - with treeshaking flags 26.18 kB - -
@sentry/browser (incl. Tracing) 46.55 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.35 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.36 kB - -
@sentry/browser (incl. Tracing, Replay) 85.81 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 75.46 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 90.52 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 103.2 kB - -
@sentry/browser (incl. Feedback) 44.93 kB - -
@sentry/browser (incl. sendFeedback) 32.54 kB - -
@sentry/browser (incl. FeedbackAsync) 37.68 kB - -
@sentry/browser (incl. Metrics) 28.83 kB - -
@sentry/browser (incl. Logs) 29.06 kB - -
@sentry/browser (incl. Metrics & Logs) 29.75 kB - -
@sentry/react 29.54 kB - -
@sentry/react (incl. Tracing) 48.81 kB - -
@sentry/vue 33.17 kB - -
@sentry/vue (incl. Tracing) 48.51 kB - -
@sentry/svelte 27.77 kB - -
CDN Bundle 30.14 kB - -
CDN Bundle (incl. Tracing) 48.52 kB - -
CDN Bundle (incl. Logs, Metrics) 31.73 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 49.82 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 70.97 kB - -
CDN Bundle (incl. Tracing, Replay) 86.03 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 87.34 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 91.83 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 93.09 kB - -
CDN Bundle - uncompressed 89.87 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.68 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 94.58 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 150.66 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 219.32 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 265.89 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 269.86 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.6 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 283.55 kB - -
@sentry/nextjs (client) 51.36 kB - -
@sentry/sveltekit (client) 46.98 kB - -
@sentry/core/server 78.71 kB +0.02% +14 B 🔺
@sentry/core/browser 65.07 kB +0.02% +11 B 🔺
@sentry/node-core 63.17 kB -0.07% -42 B 🔽
@sentry/node 125.44 kB -0.07% -81 B 🔽
@sentry/node (incl. diagnostics channel injection) 147.95 kB -0.08% -104 B 🔽
@sentry/node/import (ESM hook with diagnostics-channel injection) 70.03 kB - -
@sentry/node/light 51.32 kB +0.01% +1 B 🔺
@sentry/node - without tracing 74.67 kB -0.05% -37 B 🔽
@sentry/aws-serverless 83.88 kB -0.06% -42 B 🔽
@sentry/cloudflare (withSentry) - minified 183.93 kB - -
@sentry/cloudflare (withSentry) 455.31 kB +0.01% +28 B 🔺

View base workflow run

nicohrubec and others added 2 commits July 17, 2026 15:53
Adapts the add-ai-integration skill to require lowest-level provider
integrations to check _INTERNAL_isAiProviderSpanSuppressed(), and adds a
Bugbot rule to lint for missing suppression checks / global suppression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nicohrubec
nicohrubec marked this pull request as ready for review July 17, 2026 14:21
@nicohrubec
nicohrubec requested review from a team as code owners July 17, 2026 14:21
@nicohrubec
nicohrubec requested review from andreiborza and isaacs and removed request for a team July 17, 2026 14:21
@nicohrubec
nicohrubec marked this pull request as draft July 17, 2026 15:24

@isaacs isaacs 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.

LGTM! Some tiny nits / suggestions for a doc comment, but an improvement for sure.

/**
* Execute a callback with AI provider spans suppressed in the current scope.
* This is used by higher-level integrations (like LangChain) to prevent
* duplicate spans from underlying AI provider instrumentations.

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.

low: I think this approach means that anything that calls getCurrentScope() will get this child scope instead of the original? It seems like it might have side effects if anything in the callback calls Sentry.setTag/setContext/setUser, since it'll be attached to a discarded scope.

I don't really see a way around it, tbh, but maybe a doc comment could be worthwhile here?

Suggested change
* duplicate spans from underlying AI provider instrumentations.
* duplicate spans from underlying AI provider instrumentations.
*
* Suppression rides on a forked scope (as `suppressTracing` does) because a
* forked scope is the only Sentry-native carrier that both survives async
* boundaries and stays isolated from concurrent, unrelated work.
*
* Trade-off: current-scope mutations made inside `callback` land on the fork
* and do not propagate to the outer scope.

// EDGE CASE: Import and instantiate Anthropic client BEFORE LangChain is imported
// This simulates the timing issue where a user creates an Anthropic client in one file
// before importing LangChain in another file
// Direct Anthropic call made BEFORE LangChain is imported/used

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.

nit: this file is named scenario-openai-before-langchain.mjs but it is using anthropic? Should it be renamed scenario-anthropic-before-langchain.mjs? It doesn't change it functionally, of course, but just seems a little weird 😅

// EDGE CASE: Import and instantiate Anthropic client BEFORE LangChain is imported
// This simulates the timing issue where a user creates an Anthropic client in one file
// before importing LangChain in another file
// Direct Anthropic call made BEFORE LangChain is imported/used

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.

This one, too. Not openai?

}

/** @inheritDoc */
protected _setupIntegrations(): void {

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.

Nice. Removing this is a good cleanup.

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.

AI provider spans are suppressed globally when LangChain integration is active

2 participants