Skip to content

feat(claude-agent-sdk): add hook-indexed lifecycle instrumentation#9201

Closed
mr-lee wants to merge 55 commits into
DataDog:masterfrom
mr-lee:codex/claude-agent-sdk-hook-context
Closed

feat(claude-agent-sdk): add hook-indexed lifecycle instrumentation#9201
mr-lee wants to merge 55 commits into
DataDog:masterfrom
mr-lee:codex/claude-agent-sdk-hook-context

Conversation

@mr-lee

@mr-lee mr-lee commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • supersedes feat(claude-agent-sdk): add instrumentation for @anthropic-ai/claude-agent-sdk #7974 by keeping the Claude Agent SDK integration but moving lifecycle capture back to SDK hooks
  • preserves user-provided SDK hooks while adding tracer lifecycle hooks at query start
  • uses hook-captured lifecycle facts for session/tool/subagent semantics and a single stream index for LLM/tool payload enrichment
  • adds a sirun benchmark comparing repeated stream scans with hook-indexed lifecycle lookup
  • updates LLM Obs expectations so subagent wrapper spans consistently parent their child LLM/tool work

Why

SDK hooks are cleaner instrumentation wedges for lifecycle facts: they fire at the semantic boundary where the SDK knows session, prompt, tool, permission, and subagent state. The event stream is still useful, but it is better treated as payload/enrichment data rather than as the source of execution-context lifecycle.

This avoids trying to manufacture tracing-channel parentage around individual stream events while still keeping the tracing-channel model centered on query/LLM/tool execution contexts.

Relationship to existing PRs

Validation

  • ./node_modules/.bin/eslint packages/datadog-instrumentations/src/claude-agent-sdk.js packages/datadog-plugin-claude-agent-sdk/test/index.spec.js packages/dd-trace/test/llmobs/plugins/claude-agent-sdk/index.spec.js benchmark/sirun/plugin-claude-agent-sdk/index.js
  • node -c packages/datadog-instrumentations/src/claude-agent-sdk.js && node -c packages/datadog-plugin-claude-agent-sdk/test/index.spec.js && node -c packages/dd-trace/test/llmobs/plugins/claude-agent-sdk/index.spec.js && node -c benchmark/sirun/plugin-claude-agent-sdk/index.js
  • VARIANT=stream-scan OPERATIONS=25000 node benchmark/sirun/plugin-claude-agent-sdk/index.js
  • VARIANT=hook-indexed OPERATIONS=25000 node benchmark/sirun/plugin-claude-agent-sdk/index.js
  • Linux/testagent focused LLM Obs reproduction: SDK 0.2.113 passed after the subagent ordering fix; SDK 0.3.198 was inconclusive locally because the Docker VM hit ENOSPC before the relevant assertions
  • GitHub Actions on head 09aad76cd: claude-agent-sdk passed; broad package-size-report and Performance and correctness tests failed before checkout in DataDog/dd-octo-sts-action with missing id-token environment/permission metadata, not from this code path

mr-lee and others added 30 commits May 20, 2026 06:12
…agent-sdk

Adds automatic instrumentation for the Claude Agent SDK, providing
full visibility into agentic sessions via APM tracing and LLM Obs.

Span hierarchy aligned with trajectory-spec APPENDIX-DD-LLMOBS-MAPPING:

  agent (session)
    └── agent (turn)
         ├── tool ({tool_name})
         └── agent (subagent-{agent_type})

Key design decisions:
- Uses SDK's first-class hooks API (SessionStart, SessionEnd, Stop,
  PreToolUse, PostToolUse, SubagentStart, SubagentStop, etc.)
- Turn spans are `agent` kind (not workflow) per spec
- Dynamic span names: session, turn, {toolName}, subagent-{type}
- Model name split from provider prefix (anthropic/claude-sonnet-4-6)
- Turn output from Stop hook's last_assistant_message
- Captures cwd, transcript_path, agent_type, is_interrupt, start_trigger
- Pure ESM package handled via import-in-the-middle with esmFirst: true
- User hooks preserved via mergeHooks (user matchers before tracer matchers)

Known gap: No LLM-level spans - the Agent SDK bundles its own Anthropic
client internally, so the existing @anthropic-ai/sdk shimmer doesn't fire.
This matches dd-trace-py's approach (agent + tool spans only).

Tests: 41 APM tracing + 13 LLM Obs tests.
Root cause: OTEL env vars caused dd-trace to use OTLP exporter instead
of the agent exporter. The mock test agent only handles /v0.4/traces.

- APM test: clear OTEL_* env vars before tracer init
- LLM Obs test: rewrite to test span event structure directly
- Instrumentation: finishSession closes pending child spans first
- VCR proxy: use canonical body for stable cassette hashing
Add await before origIterator.return/throw calls to satisfy
require-await lint rule. Bump minimum SDK version from 0.2.0 to
0.2.1 in rewriter versionRange and test withVersions filter — 0.2.0
uses require() in ESM scope which breaks on Node 22.
…solution

The LLM Obs test was missing the withVersions wrapper that sets NODE_PATH
to the versioned SDK's node_modules directory. Without it, the bare
require('@anthropic-ai/claude-agent-sdk') fails with "Cannot find module".
This matches the pattern used by the APM test.
…o turn spans

Turn spans are now root spans carrying all session metadata (model, start_trigger,
project_dir, permission_mode, agent_type, transcript_path). Session-level span
removed — the turn is the primary unit of work.
…g/dd-trace-js into feat/claude-agent-sdk-integration
@datadog-official

datadog-official Bot commented Jul 2, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 150 Pipeline jobs failed

All Green | all-green   View in Datadog   GitHub Actions

CodeQL | Analyze (javascript)   View in Datadog   GitHub Actions

Project | package-size-report   View in Datadog   GitHub Actions

View all 150 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🔄 Datadog auto-retried 1 job - 1 passed on retry View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: c745092 | Docs | Datadog PR Page | Give us feedback!

@mr-lee

mr-lee commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing this fork duplicate to avoid split CI/check-run state. Canonical upstream PR is #9202.

@mr-lee mr-lee closed this Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants