Skip to content

Add logfire.instrument_claude_agent_sdk()#1799

Merged
alexmojaki merged 148 commits intomainfrom
instrument-claude-agent-sdk
Mar 27, 2026
Merged

Add logfire.instrument_claude_agent_sdk()#1799
alexmojaki merged 148 commits intomainfrom
instrument-claude-agent-sdk

Conversation

@alexmojaki
Copy link
Copy Markdown
Collaborator

@alexmojaki alexmojaki commented Mar 24, 2026

Summary

  • Adds logfire.instrument_claude_agent_sdk() for native OpenTelemetry instrumentation of the Claude Agent SDK
  • Monkey-patches ClaudeSDKClient to create conversation, turn, and tool spans
  • Injects tracing hooks (PreToolUse, PostToolUse, PostToolUseFailure) for tool call tracing
  • Records usage metrics, cost, session metadata on the root conversation span
  • Uses threading.local() for context propagation (workaround for anyio breaking contextvars)

Test plan

  • 45 tests in tests/otel_integrations/test_claude_agent_sdk.py covering all code paths
  • 100% coverage on logfire/_internal/integrations/claude_agent_sdk.py
  • test_logfire_api.py updated to include the new integration
  • CI green (pyright, ruff, pytest all passing)

🤖 Generated with Claude Code


Summary by cubic

Adds native logfire.instrument_claude_agent_sdk() tracing for the Claude Agent SDK using OTel GenAI semconv. Creates invoke_agent, chat {model}, and execute_tool {tool_name} spans with provider, messages (incl. reasoning), per‑turn usage/cost, conversation ID, and error details.

  • New Features

    • Instruments claude_agent_sdk.ClaudeSDKClient via SDK hooks (scope suffix claude_agent_sdk); returns a context manager to revert; API exposed in logfire and logfire_api (stub uses nullcontext()).
    • Span model: root invoke_agent; sibling chat {model} per assistant turn; sibling execute_tool {tool_name} for tool calls.
    • Records gen_ai.provider.name, gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, reasoning via ReasoningPart (ThinkingBlock), per‑turn gen_ai.usage.* (incl. cache), gen_ai.response.model, gen_ai.conversation.id, operation.cost, and error.type; sets error level on failures.
    • Captures prompt from .query(); only ClaudeSDKClient is instrumented; instances created after instrumentation get full tool tracing. Docs updated to use logfire.instrument_claude_agent_sdk().
  • Bug Fixes

    • Idempotent instrumentation and clean uninstrument; guard duplicate hook injection (incl. shared/default options).
    • Correct token/cache counts; reset prompt per call; set error level on failed turns/tool calls; cleanup orphaned tool spans.
    • Stability/typing: import claude_agent_sdk at module import; use HookMatcher; isinstance checks for SDK block types; replace getattr with direct attribute access on typed SDK blocks; tighten types; remove dead code; add coverage pragmas; fix sys.modules leak.
    • Tests: cassette‑based integration via a fake claude CLI with --record-claude-cassettes; add dev dependency claude-agent-sdk; CI/resource warning fixes.

Written for commit 09a9ce9. Summary will update on new commits.

Reset _logfire_prompt at the start of patched_query() so it doesn't
carry over from a previous call when prompt is None.

Also remove unused scaffolding: _logfire_start_time, _logfire_streamed_input,
_next_start_time, and mark_next_start() — all stored but never read.
cubic-dev-ai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 25, 2026

Deploying logfire-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: b096c58
Status: ✅  Deploy successful!
Preview URL: https://b46dbff3.logfire-docs.pages.dev
Branch Preview URL: https://instrument-claude-agent-sdk.logfire-docs.pages.dev

View logs

original_init sets self.options to ClaudeAgentOptions() when no options
are passed. The kwargs extraction would miss this, skipping hook injection.
devin-ai-integration[bot]

This comment was marked as resolved.

alexmojaki and others added 2 commits March 27, 2026 15:34
…tion

Tool responses from the SDK are already structured data. Removing the
str() conversion lets them serialize as proper JSON objects in both the
execute_tool span attribute and conversation history input messages.

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

Consistency with post_tool_use_hook and post_tool_use_failure_hook which
already access input_data inside the error handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

alexmojaki and others added 4 commits March 27, 2026 15:48
Prevents stale references (including conversation history) from
persisting in thread-local storage after receive_response completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Chat spans now include gen_ai.system_instructions so each span is
self-contained. Span name is updated to 'chat {model}' via update_name
when the model is known.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

alexmojaki and others added 4 commits March 27, 2026 16:23
Cross-async-context detach always fails with OTel error logs, and
keeping context attached doesn't help (tool code runs in yet another
async context). Now: attach root context, _start() the span to capture
the parent link, immediately detach. No context left attached means no
cross-context detach errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The model comes from AssistantMessage (not ResultMessage), so we store
it on the turn tracker and set it on the root span when the result arrives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

alexmojaki and others added 3 commits March 27, 2026 16:57
Replaces scattered thread-local attributes and the global
_active_tool_spans dict with a single _ConversationState object.
This fixes cross-thread interference where two conversations would
share the same _active_tool_spans dict.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alexmojaki alexmojaki changed the title Add native Claude Agent SDK integration Add logfire.instrument_claude_agent_sdk() Mar 27, 2026
@alexmojaki alexmojaki enabled auto-merge (squash) March 27, 2026 17:26
@alexmojaki alexmojaki merged commit 6de926b into main Mar 27, 2026
17 checks passed
@alexmojaki alexmojaki deleted the instrument-claude-agent-sdk branch March 27, 2026 17:33
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.

1 participant