Skip to content

claude_agent_sdk integration: gaps in instrumentation coverage #149

@AbhiPrasad

Description

Overview

A review of the claude_agent_sdk integration against the Claude Agent SDK Python docs found several missing pieces. Tracking them here in priority order.


1. ThinkingBlock not serialized with a type field

BlockClassName and SERIALIZED_CONTENT_TYPE_BY_BLOCK_CLASS in _constants.py only cover TextBlock, ToolUseBlock, and ToolResultBlock. When extended thinking is enabled, ThinkingBlock objects are serialized via dataclasses.asdict() (producing {"thinking": "...", "signature": "..."}), but no "type": "thinking" key is added — unlike every other block type.

Fix: Add THINKING = "ThinkingBlock" to BlockClassName and a "ThinkingBlock" → "thinking" entry to SERIALIZED_CONTENT_TYPE_BY_BLOCK_CLASS.


2. Top-level query() function not instrumented

patchers.py patches ClaudeSDKClient and SdkMcpTool but not the standalone claude_agent_sdk.query() function (the one-shot API). Users calling await query(prompt=...) directly get zero tracing.

Fix: Add a patcher for claude_agent_sdk.query that wraps it with similar span lifecycle logic to WrappedClaudeSDKClient.query + receive_response.


3. ResultMessage.result not captured as root span output

_handle_result reads usage, num_turns, and session_id, but ignores the result field — the canonical final text answer from the agent. The root span output currently comes from the last accumulated assistant message in _final_results, which may differ.

Fix: If message.result is set, use it as (or log it alongside) the root span output.


4. ResultMessage.is_error not handled

When an agent run fails, ResultMessage.is_error=True, but _handle_result never checks this, so the root span won't reflect the error state.

Fix: Check getattr(message, "is_error", None) in _handle_result and call self._root_span.log(error=...) when true.


5. Useful ResultMessage fields not captured

Several fields are silently dropped:

Field Value
stop_reason Why the agent stopped
total_cost_usd Cost of the run
duration_ms Wall-clock latency
duration_api_ms API latency

Fix: Include these in result_metadata logged to the root span in _handle_result.


6. AssistantMessage.error not logged

The SDK docs show AssistantMessage has an error field. _handle_assistant never checks it, so model-returned errors in assistant messages are silently discarded.

Fix: Check getattr(message, "error", None) in _handle_assistant and log it on the LLM span.


7. receive_messages() not instrumented (lower priority)

Wrapper.__getattr__ proxies receive_messages() directly to the underlying client without tracing. This is lower priority since receive_response() is the recommended path, but it's a gap if users use the raw event stream.


Not gaps (intentional)

  • StreamEvent — raw per-token streaming events; intentionally ignored in ContextTracker.add()
  • interrupt() — control method; no tracing needed
  • All hook types (PreToolUse, PostToolUse, SubagentStart, SubagentStop, etc.) — handled correctly via the generic _hook_event_name() path

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions