Debugging and Error Handling — study note
Debugging and Error Handling — study note
Domain 4 of the Claude Certified Developer – Foundations exam covers one skill, Debugging and Error Handling. The habit it rewards is placing a failure before fixing it:
- a failed request (an error);
- a successful response that stopped for a reason (a stop reason);
- a fault in your integration;
- a problem in what the model was given.
This note summarizes the topic deck and the pages behind it.
Errors: the status says who acts
| Status | Meaning | What to do |
|---|---|---|
| 400 | Format or content of the request, or a spend limit you set | Fix the request, or review the limit |
| 401 / 403 | The key, or its permission | Fix the credential or access |
| 409 | Conflict with a resource's current state | Resolve it, then retry |
| 413 | Request too large | Shrink or split it |
| 429 | Rate limit, or a spend cap | Back off; a spend-cap 429 keeps failing until access resumes |
| 500 / 529 | Internal error / temporary overload | Retry with exponential backoff |
- Errors are JSON with an
errorobject holding atypeand amessage, plus a request ID. Keep the request ID for support. - Catch the SDK's typed exceptions, most specific first. Never string-match messages.
- The official SDKs already retry transient failures (connection errors, rate limits, 5xx) with backoff.
- Streams can fail after a 200, and those errors arrive as events, not as an HTTP status.
- If traffic jumps sharply, ramp it up gradually to avoid acceleration limits.
Stop reasons: a 200 still needs checking
| stop_reason | What to do |
|---|---|
end_turn | Use the response |
max_tokens | Raise the limit, or continue the response |
stop_sequence | Read stop_sequence to see which one fired |
| Context window full | Treat as truncated (see below) |
pause_turn | A server-tool loop paused: send the response back as-is |
tool_use | Run your tool and send a tool_result |
refusal | Arrives as a normal HTTP 200: read stop_details and retry on a fallback model |
The context-window row is the stop reason
model_context_window_exceeded. Newer models return it; earlier models need a beta header.
When you truncate, append a notice so readers know the output is incomplete. An empty end_turn right after tool results usually means text was added after the tool_result blocks: stop adding it, and don't resend the empty response unchanged. When streaming, stop_reason arrives in the message_delta event.
Integration fault or model output?
| Symptom | Where the fault lives | Fix |
|---|---|---|
| 400: tool calls left without results | Your integration | One result per call, before any text |
| Tools called one per turn | Your integration | All results in one user message |
| Thinking blocks cannot be modified | Your integration | Send the assistant message back unchanged |
| Wrong tool chosen | The descriptions | Say when to use each tool |
| Wrong parameter types | The schema | Strict mode (supported subset) or input examples |
| Claude refuses to act on a tool result, or asks to confirm its instructions | Where you put them | Move them to a user turn after the result, or (supported models) a mid-conversation system message |
| String match on tool input breaks | Your integration | Parse the JSON: escaping differs by model version |
In the API these are tool_use and tool_result blocks matched by id; the schema fixes are strict: true (if the schema is in the supported subset) and input_examples on the tool definition; the instruction fix is a user turn after the result.
Traces: turn, API call, tool call
With tracing enabled (in beta, so names may change), each step becomes a span:
- the turn: one prompt to one response;
- each API call: model, latency and token counts;
- each tool call: with separate children for the permission wait and the execution.
Failed or aborted requests may omit token counts. Content is not recorded by default. Start an agent run while one of your application's spans is active, and it appears inside your application's trace.