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 Done ✅
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 Done ✅
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 Done ✅
_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 Done ✅
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
Overview
A review of the
claude_agent_sdkintegration against the Claude Agent SDK Python docs found several missing pieces. Tracking them here in priority order.## 1.Done ✅ThinkingBlocknot serialized with atypefieldBlockClassNameandSERIALIZED_CONTENT_TYPE_BY_BLOCK_CLASSin_constants.pyonly coverTextBlock,ToolUseBlock, andToolResultBlock. When extended thinking is enabled,ThinkingBlockobjects are serialized viadataclasses.asdict()(producing{"thinking": "...", "signature": "..."}), but no"type": "thinking"key is added — unlike every other block type.Fix: Add
THINKING = "ThinkingBlock"toBlockClassNameand a"ThinkingBlock" → "thinking"entry toSERIALIZED_CONTENT_TYPE_BY_BLOCK_CLASS.## 2. Top-levelDone ✅query()function not instrumentedpatchers.pypatchesClaudeSDKClientandSdkMcpToolbut not the standaloneclaude_agent_sdk.query()function (the one-shot API). Users callingawait query(prompt=...)directly get zero tracing.Fix: Add a patcher for
claude_agent_sdk.querythat wraps it with similar span lifecycle logic toWrappedClaudeSDKClient.query+receive_response.## 3.Done ✅ResultMessage.resultnot captured as root span output_handle_resultreadsusage,num_turns, andsession_id, but ignores theresultfield — 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.resultis set, use it as (or log it alongside) the root span output.4.
ResultMessage.is_errornot handledWhen an agent run fails,
ResultMessage.is_error=True, but_handle_resultnever checks this, so the root span won't reflect the error state.Fix: Check
getattr(message, "is_error", None)in_handle_resultand callself._root_span.log(error=...)when true.## 5. UsefulDone ✅ResultMessagefields not capturedSeveral fields are silently dropped:
stop_reasontotal_cost_usdduration_msduration_api_msFix: Include these in
result_metadatalogged to the root span in_handle_result.6.
AssistantMessage.errornot loggedThe SDK docs show
AssistantMessagehas anerrorfield._handle_assistantnever checks it, so model-returned errors in assistant messages are silently discarded.Fix: Check
getattr(message, "error", None)in_handle_assistantand log it on the LLM span.7.
receive_messages()not instrumented (lower priority)Wrapper.__getattr__proxiesreceive_messages()directly to the underlying client without tracing. This is lower priority sincereceive_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 inContextTracker.add()interrupt()— control method; no tracing needed_hook_event_name()path