feat(javascript): Add Claude Code Agent SDK instrumentation
Summary
Adds Sentry tracing instrumentation for the @anthropic-ai/claude-agent-sdk (Claude Code Agent SDK) following OpenTelemetry Semantic Conventions Sentry's Agent Monitoring.
This integration enables AI monitoring for Claude Code agents with comprehensive telemetry:
- Agent invocation spans (
invoke_agent) - LLM chat spans (
chat) - Tool execution spans (
execute_tool) - Token usage tracking (including cache metrics)
- Model info and session tracking
- Optional input/output recording
Key Implementation Details
Why Not Automatic Like Other AI Integrations?
The Claude Code SDK (@anthropic-ai/claude-agent-sdk) is ESM-only with no CommonJS build, which prevents automatic instrumentation via OpenTelemetry's require() hooks that work for other integrations (Anthropic AI, OpenAI, etc.).
Solution: Helper Function Pattern
Provides createInstrumentedClaudeQuery() - a one-line helper that:
- Lazy loads the SDK via dynamic
import()(avoids bundler issues) - Automatically retrieves options from
claudeCodeIntegration()config - Uses global singleton pattern (patches once, reuses everywhere)
- Works with any bundler (Next.js/webpack, Vite, etc.)
Usage
// Step 1: Configure in Sentry init
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'your-dsn',
integrations: [
Sentry.claudeCodeIntegration({
recordInputs: true,
recordOutputs: true
})
],
});
// Step 2: Use in your code (1 line!)
import * as Sentry from '@sentry/node';
const query = Sentry.createInstrumentedClaudeQuery();
// Use query as normal - automatically instrumented
for await (const message of query({
prompt: 'Hello',
options: { model: 'claude-sonnet-4-5' }
})) {
console.log(message);
}
Remaining TODO's
- [ ] Tests
- [ ] Handle PII (disabling prompt transmission etc...)
- [ ] Expanded JavaScript server SDKs
size-limit report 📦
| Path | Size | % Change | Change |
|---|---|---|---|
| @sentry/browser | 24.64 kB | - | - |
| @sentry/browser - with treeshaking flags | 23.14 kB | - | - |
| @sentry/browser (incl. Tracing) | 40.99 kB | - | - |
| @sentry/browser (incl. Tracing, Replay) | 79.31 kB | - | - |
| @sentry/browser (incl. Tracing, Replay) - with treeshaking flags | 68.99 kB | - | - |
| @sentry/browser (incl. Tracing, Replay with Canvas) | 84.02 kB | - | - |
| @sentry/browser (incl. Tracing, Replay, Feedback) | 96.17 kB | - | - |
| @sentry/browser (incl. Feedback) | 41.33 kB | - | - |
| @sentry/browser (incl. sendFeedback) | 29.3 kB | - | - |
| @sentry/browser (incl. FeedbackAsync) | 34.26 kB | - | - |
| @sentry/react | 26.35 kB | - | - |
| @sentry/react (incl. Tracing) | 42.99 kB | - | - |
| @sentry/vue | 29.13 kB | - | - |
| @sentry/vue (incl. Tracing) | 42.79 kB | - | - |
| @sentry/svelte | 24.66 kB | - | - |
| CDN Bundle | 26.94 kB | - | - |
| CDN Bundle (incl. Tracing) | 41.65 kB | - | - |
| CDN Bundle (incl. Tracing, Replay) | 77.91 kB | - | - |
| CDN Bundle (incl. Tracing, Replay, Feedback) | 83.37 kB | - | - |
| CDN Bundle - uncompressed | 78.95 kB | - | - |
| CDN Bundle (incl. Tracing) - uncompressed | 123.53 kB | - | - |
| CDN Bundle (incl. Tracing, Replay) - uncompressed | 238.57 kB | - | - |
| CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed | 251.33 kB | - | - |
| @sentry/nextjs (client) | 45.13 kB | - | - |
| @sentry/sveltekit (client) | 41.42 kB | - | - |
| @sentry/node-core | 50.78 kB | +0.01% | +1 B 🔺 |
| @sentry/node | 154.43 kB | +0.02% | +25 B 🔺 |
| @sentry/node - without tracing | 92.66 kB | - | - |
| @sentry/aws-serverless | 106.35 kB | -0.01% | -2 B 🔽 |
node-overhead report 🧳
Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
| Scenario | Requests/s | % of Baseline | Prev. Requests/s | Change % |
|---|---|---|---|---|
| GET Baseline | 9,090 | - | 8,754 | +4% |
| GET With Sentry | 1,399 | 15% | 1,355 | +3% |
| GET With Sentry (error only) | 6,127 | 67% | 6,114 | +0% |
| POST Baseline | 1,207 | - | 1,187 | +2% |
| POST With Sentry | 515 | 43% | 509 | +1% |
| POST With Sentry (error only) | 1,057 | 88% | 1,058 | -0% |
| MYSQL Baseline | 3,336 | - | 3,250 | +3% |
| MYSQL With Sentry | 475 | 14% | 467 | +2% |
| MYSQL With Sentry (error only) | 2,729 | 82% | 2,647 | +3% |
Thanks for working on this! For the first pass, the biggest lift here is to try to auto patch the functions we need automatically instead of asking user to import patched method, then we can move to tackling the other TODOs you have
Thanks SO much for all these. I'll get started on them.
I tried REALLY hard to figure out how to hook into the existing query, and I couldn't get it to work no matter what I tried. I'll chat with you in slack on it, but I'd love some advice / guidance. I tried a bunch of different angles - but each time I ran into effectively timing issues where we couldn't hook fast enough. Felt like a limitation on how Claude Code's SDK works - but could be a total skill issue on my side.
Thanks for working on this! For the first pass, the biggest lift here is to try to auto patch the functions we need automatically instead of asking user to import patched method, then we can move to tackling the other TODOs you have
Thanks SO much for all these. I'll get started on them.
I tried REALLY hard to figure out how to hook into the existing query, and I couldn't get it to work no matter what I tried. I'll chat with you in slack on it, but I'd love some advice / guidance. I tried a bunch of different angles - but each time I ran into effectively timing issues where we couldn't hook fast enough. Felt like a limitation on how Claude Code's SDK works - but could be a total skill issue on my side.
Hello @codyde, are you still working on this? if not, let's close this, we're trying to clean up the stale PRs
Thanks for working on this! For the first pass, the biggest lift here is to try to auto patch the functions we need automatically instead of asking user to import patched method, then we can move to tackling the other TODOs you have
Thanks SO much for all these. I'll get started on them. I tried REALLY hard to figure out how to hook into the existing query, and I couldn't get it to work no matter what I tried. I'll chat with you in slack on it, but I'd love some advice / guidance. I tried a bunch of different angles - but each time I ran into effectively timing issues where we couldn't hook fast enough. Felt like a limitation on how Claude Code's SDK works - but could be a total skill issue on my side.
Hello @codyde, are you still working on this? if not, let's close this, we're trying to clean up the stale PRs
I definitely am! I pushed up a few more commits today that included fixes for some of the other items you mentioned - but im struggling to get through this proxy one. I might need some pairing time to take a look at it together since im less familiar with the functionality.