CCDVF-D4.1 · Domain 4 · Evaluation, Testing & Debugging · 2.6% of CCD-F

Debugging LLM System Failures.

6 min read·8 sections·Tier A

A Claude application fails in two structurally different places: the integration layer (the call never reached a valid model turn - bad auth, malformed request, rate limit) or model output (the call returned 200, but the content is wrong - refused, truncated, or malformed). The error object only exists on non-2xx responses; a 200 with a bad stop_reason is not an HTTP error, so your code has to detect it itself. Anthropic API errors reference

Official Anthropic API + SDK docsCCD-F Domain 4 · DebuggingCCD-F + CCA-P + CCA-A
Debugging LLM System Failures, hero illustration featuring Loop mascot in a warm gallery scene.
Domain CCDVF-D4Evaluation, Testing & Debugging · 2.6%
On this page
01 · Summary

TLDR

A Claude application fails in two structurally different places: the integration layer (the call never reached a valid model turn - bad auth, malformed request, rate limit) or model output (the call returned 200, but the content is wrong - refused, truncated, or malformed). The error object only exists on non-2xx responses; a 200 with a bad stop_reason is not an HTTP error, so your code has to detect it itself. Anthropic API errors reference

11
Typed HTTP error codes
CCDVF-D4
Exam domain
CCA-P D4 + CCA-A D7
Also tested in
2
Default SDK retries
3
Triage layers
02 · Definition

What it is

A production Claude call can fail in two structurally different places, and each needs a different fix. The integration layer covers everything before a valid model turn happens: bad auth, a malformed request, a rate limit, or a transient overload - these surface as non-2xx HTTP status codes with a typed error object (type, message, request_id). Model output failures are different in kind: the call returned 200, was billed, and completed - but the content itself is wrong, refused, truncated, or malformed. Confusing the two leads to the most common production debugging mistake: wrapping every call in a generic retry loop and shipping a truncated or refused response as if it were valid.

Origin isolation - deciding which of these two layers a given failure belongs to, before writing a fix - is the core skill this objective tests. It composes with two further steps: identifying the specific error TYPE once the layer is known, and selecting a recovery strategy that matches that type (retry with backoff, fix the request, raise max_tokens, adjust the prompt/policy, or enforce structure) rather than applying one generic reaction to every failure.

03 · Mechanics

How it works

Typed, predictable HTTP codes drive the first triage split. 400 invalid_request_error, 401 authentication_error, 402 billing_error, 403 permission_error, 404 not_found_error, 409 conflict_error, and 413 request_too_large are all non-retryable - the request itself is broken, so blind retry just repeats the failure. 429 rate_limit_error, 500 api_error, 504 timeout_error, and 529 overloaded_error are transient - retry with backoff genuinely helps. Getting this split backwards (retrying a 400, or treating a 429 as a code bug) is the most-tested distractor pattern.

SDKs retry automatically, but only within that transient set, and only twice by default. Connection errors, 429s, and 5xx responses are retried with exponential backoff honoring retry-after, configurable via a max_retries option per client. 429 responses also expose x-ratelimit-remaining-* headers for proactive throttling before a limit is hit. Catch typed SDK exceptions (e.g. Python anthropic.NotFoundError, anthropic.RateLimitError, anthropic.APIStatusError for any other non-2xx, anthropic.APIConnectionError for a network failure before any response existed) rather than string-matching error messages, and handle the most specific exception first. Every response - success or failure - carries a request-id for support correlation, the primary trace artifact for isolating a failed call.

Model-output failures live inside a 200 and surface only through `stop_reason`. stop_reason: "refusal" means Claude declined for safety - the call is 200 and billed, and the output may not match your schema because the refusal message takes precedence over schema constraints. stop_reason: "max_tokens" means truncation - the response may be incomplete and not match your schema either. Neither raises an exception; both require branching on stop_reason before the payload is trusted, which is precisely what a try/except around typed exceptions alone will not catch.

Structured Outputs and Strict Tool Use close the remaining JSON-quality gap, but they are two separate mechanisms, not one setting. Structured Outputs is configured via output_config.format on the request and constrains the model's final text response to a JSON Schema. Strict Tool Use is configured with a top-level strict: true field on an individual tool definition (alongside name, description, and input_schema) and constrains that tool's tool_use.input to its schema. output_config.format does not take a tool-level strict: true parameter - they are configured independently, on different objects, for different output paths (final response vs. a specific tool call), even though both compile to grammar-constrained decoding through the same underlying pipeline. Neither overrides a refusal or rescues a truncated response. One residual gap even under strict mode: enum/const values can differ only in leading-word capitalization with no error and no distinguishing stop_reason, so compare enum output case-insensitively rather than assuming exact-match. Exceeding schema complexity limits (20 strict tools per request, 24 optional params, 16 union-typed params) pushes the failure back to the integration layer entirely - a 400 before generation ever starts.

Origin isolation also covers prompt-design and model-selection failures, one level above HTTP/`stop_reason` mechanics. Not every wrong answer is a refusal or truncation - a well-formed, schema-compliant 200 can still be a hallucination (confident fabrication with no refusal signal), a prompt failure (ambiguous instructions producing a technically valid but off-target answer), or a model mismatch (a lighter/cheaper model handling a task past its reliability ceiling for that complexity). None of these raise a typed exception or an unusual stop_reason - they surface only through evaluation against ground truth or ground-truth comparison against a stronger model, which is why an architect's diagnostic toolkit extends past dev-level error handling into eval-driven root-causing.

Debugging LLM System Failures mechanics, painterly diagram featuring Loop mascot.
04 · In production

Where you'll see it

Ticket-extraction pipeline under load

Triage order: (1) did the call raise RateLimitError/APIStatusError - integration layer, fix with backoff/queuing; (2) if 200, read stop_reason first - max_tokens means raise the budget, refusal means fix policy/prompt, not retry; (3) only if stop_reason is end_turn/tool_use and JSON still fails to parse is it a genuine content-quality issue, fixed with output_config.format or strict: true.

Non-technical practitioner triage

A delivery consultant gets a vague, off-target answer from a client-facing Claude workflow - the CCAOF-D7-O1 framing treats this as a prompt-iteration problem (add specificity, examples, explicit format) before escalating it as a system-reliability incident.

05 · Compare

Side-by-side

Failure classHTTP statusSignalFix approach
Integration - transient429 / 500 / 504 / 529Typed error, retryableRetry with backoff (SDK does this automatically, twice by default)
Integration - non-retryable400 / 401 / 402 / 403 / 404 / 409 / 413Typed error, permanentFix the request/auth/schema; blind retry repeats the failure
Model output - refused200stop_reason: "refusal"Not an API error; adjust policy/prompt, don't retry the same call
Model output - truncated200stop_reason: "max_tokens"Raise the token budget; response may not match schema
Model output - malformed/non-compliant200end_turn/tool_use but JSON fails to parse or match schemaoutput_config.format / strict: true; compare enums case-insensitively
06 · Per certification

How each cert tests this

CCD-F

Developer (D4 · Evaluation, Testing & Debugging, CCDVF-D4-O1): tests the mechanical triage - typed HTTP error identification, retryable-vs-not classification, SDK exception handling, trace correlation via request_id, and isolating a failure to the integration layer vs. the model-output layer via stop_reason.

CCA-P

Architect - Professional (D4 · Evaluation, Testing & Optimization, CCARP-D4-O4): tests diagnosing system issues ONE LEVEL UP from HTTP mechanics - is a wrong-but-well-formed 200 a prompt failure (ambiguous instructions), a hallucination (confident fabrication), or a model mismatch (wrong model tier for the task's complexity)? None of these raise an exception or an unusual stop_reason.

CCA-A

Associate - Foundations (D7 · Troubleshooting & Optimization, CCAOF-D7-O1): tests the non-technical entry point into the same tree - recognizing that a vague, off-target, or poorly-formatted output is usually a PROMPT problem to iterate on (more specificity, examples, explicit format), not a system error requiring an engineering escalation.

07 · On the exam

Question patterns

Debugging LLM System Failures exam trap, painterly cautionary scene featuring Loop mascot.
A call returns `429 rate_limit_error`. What's the correct recovery strategy?
Retry with exponential backoff, honoring retry-after - this is exactly what the SDK does automatically (twice by default). The distractor "the request itself is malformed and needs to be rewritten" misclassifies a transient, retryable error as a permanent one; 429 means slow down, not fix the payload.
A call returns `400 invalid_request_error`. Will retrying the identical payload after a short delay eventually succeed?
No - 400 is non-retryable; the request itself is broken and needs to be fixed, not resent. The distractor "retrying with the same payload after a short delay will eventually succeed" treats a permanent client error as if it were transient like a 429 or 529.
A call returns `200` with `stop_reason: "refusal"`. Is this a 4xx client error that a retry loop should catch?
No - it's a successful, billed 200 response; Claude declined for safety, and the output may not match your schema because the refusal message overrides schema constraints. The distractor "this is a 4xx client error that should be retried with an adjusted prompt" misplaces a model-output failure in the integration layer, where typed-exception handling would never catch it.
A pipeline sets `output_config.format` on the request and separately sets `strict: true` on one of its tool definitions. Does either guarantee a schema-valid response when `stop_reason` is `"refusal"`?
No - output_config.format (Structured Outputs, constrains the final response) and strict: true (Strict Tool Use, a tool-level field constraining that tool's input) are two independent mechanisms that both use constrained decoding for the case where generation completes cleanly; neither overrides a refusal or rescues a truncated (max_tokens) response. The distractor "strict: true is a parameter of output_config.format" conflates two separately-configured mechanisms into one.
A well-formed, schema-compliant 200 response confidently states a fact that is entirely fabricated - no refusal, no truncation, no exception raised. What kind of failure is this, and how is it caught?
A hallucination - a model-output content-quality failure that produces neither an HTTP error nor an unusual stop_reason, so it can only be caught via evaluation against ground truth, not exception handling. The distractor "this raises APIStatusError because the content failed internal Anthropic validation" invents a validation mechanism that doesn't exist for factual content; there is no API-level fact-check.
08 · FAQ

Frequently asked

Does a 200 status mean the response content can be trusted?
No - 200 only means the call completed and was billed. Refusal, truncation, hallucination, and schema-noncompliant content can all arrive inside a 200; check stop_reason and, beyond that, evaluate content quality separately.
Do typed SDK exceptions (RateLimitError, APIStatusError, etc.) replace the need to check `stop_reason`?
No - typed exceptions only fire on non-2xx responses. A successful call still needs its stop_reason inspected before the payload is trusted; exception handling and stop_reason branching cover different failure layers.
09 · Practice with AI

Work this with your AI

Work this concept hands-on with Claude Code, Codex, or claude.ai. Copy a prompt, paste it into your assistant, and practise in tandem. Each one keeps you active (explain it back, get drilled, or build) rather than just reading.

  • Drill it like the exam (scenario MCQs)
    Practice in the exam's scenario-MCQ format with trap awareness.
  • Explain it back (Feynman)
    Build durable, transferable understanding of a concept you can half-state.
  • Test me, adapting the difficulty
    Active recall practice on a concept you think you know.
  • Check my prerequisites first
    Before studying a concept that keeps not sticking.
  • Find the high-leverage 20%
    When a domain feels too big and you are short on time.
Self-check

Test yourself

Three diagnostic questions on this primitive. Reveal each answer when you have a guess. Want a full 60-question mock? Open the mock hub →

Q1A call returns `429 rate_limit_error`. What's the correct recovery strategy?
Retry with exponential backoff, honoring retry-after - this is exactly what the SDK does automatically (twice by default). The distractor "the request itself is malformed and needs to be rewritten" misclassifies a transient, retryable error as a permanent one; 429 means slow down, not fix the payload.
Q2A call returns `400 invalid_request_error`. Will retrying the identical payload after a short delay eventually succeed?
No - 400 is non-retryable; the request itself is broken and needs to be fixed, not resent. The distractor "retrying with the same payload after a short delay will eventually succeed" treats a permanent client error as if it were transient like a 429 or 529.
Q3A call returns `200` with `stop_reason: "refusal"`. Is this a 4xx client error that a retry loop should catch?
No - it's a successful, billed 200 response; Claude declined for safety, and the output may not match your schema because the refusal message overrides schema constraints. The distractor "this is a 4xx client error that should be retried with an adjusted prompt" misplaces a model-output failure in the integration layer, where typed-exception handling would never catch it.
Last reviewed: 2026-05-04·Refresh cadence: monthly
CCDVF-D4.1 · CCDVF-D4 · Evaluation, Testing & Debugging

Debugging LLM System Failures, complete.

You've covered the full ten-section breakdown for this primitive, definition, mechanics, code, false positives, comparison, decision tree, exam patterns, and FAQ. One technical primitive down on the path to CCA-F.

More platforms →