Debugging the Agent Transcript
Explore methods to debug agent loops built with the Claude Client SDK by implementing structured logging at each iteration stage. Learn how to trace failing runs, interpret logs of API calls, tool usage, and stop reasons, and identify common bugs like missing assistant turns. This lesson equips you to produce readable, comprehensive error diagnostics to improve reliability and maintainability of Claude-powered AI agents.
The loop from the previous lessons works, but when it breaks, there is nothing to inspect. The messages array changes on every iteration, tool calls come and go, and errors surface only as wrong output or silent None returns. Without visibility into what happened between the first API call and the final answer, debugging is guesswork.
In this lesson, we instrument the loop with structured logging that prints state at each decision point. We then use that output to trace a failing run and identify the exact turn where it went wrong.
By the end of this lesson, we will have:
A logging helper that formats message-array turns in human-readable form
Turn-level log output for every API call, stop reason, tool call, and tool result
A failing transcript annotated to show where and why it breaks
A methodology for reading agent transcripts that applies to any loop
What to log and why
A loop produces four types of events worth capturing, each at a different moment in the iteration:
Event | When It Fires | What to Capture |
API call | Before | Turn count, message count, input token estimate |
Stop reason | After the response arrives |
|
Tool dispatch | Before each tool function runs | Tool name, input arguments |
Tool result | After each tool function returns | Tool name, result string (truncated if long) |
These four points cover the full life cycle of one iteration. Any failure in the loop, wrong stop reason handling, missing tool result, ID mismatch, or truncation will be visible in at least one of these log lines.
The logging helper
Rather than scattering print() calls throughout the loop, we write one helper that formats any event as a consistent, scannable line:
import jsondef log(event: str, **kwargs) -> None:parts = [f"[{event}]"]for key, value in kwargs.items():if isinstance(value, (dict, list)):parts.append(f"{key}={json.dumps(value, default=str)[:120]}")else:parts.append(f"{key}={value}")print(" ".join(parts))
Line 1: We import
jsonto serialize dicts and lists inline.Line 3:
logtakes an event name and any number of keyword arguments. The caller decides what context to attach.Lines 4–8: We build a ...