ScriptsHub Technologies Global

AI Feature Error Handling: Seven Failures, One Catch Block

QUICK SUMMARY

An AI endpoint has seven distinct failure classes and most codebases handle one. Three usually arrive as HTTP errors: timeout, rate limit, provider overload. Four arrive inside a successful HTTP 200: refusal, content filter rejection, malformed structured output, truncated stream. Our team at ScriptsHub Technologies classified every provider signal into a semantic failure type at the boundary, branched the UI on that type rather than a status code, and retried only four of the seven. No model, prompt or retrieval change was involved.

AI feature error handling becomes critical the moment your application leaves staging. In production, users tell you it “just spins”, or that it “said it couldn’t help”, or that it “showed half an answer and then stopped”. You open the code and find a single catch block, a single toast, and a single message: Something went wrong. Please try again.

If that is your setup, your AI feature error handling covers one failure mode and ships the other six to users unlabelled. These were the symptoms our team at ScriptsHub Technologies met while building AI features into client-facing applications for a B2B software company. The fix had almost nothing to do with the model.

Why Does AI Feature Error Handling Break a Normal Try/Catch?

A conventional API has a clean contract: it returns your data or it returns an error, and the status code tells you which. You can build reliable handling on that promise, because the transport layer knows what happened.

An AI endpoint breaks the promise in three ways. It is non-deterministic, so the same request can succeed once and fail the next time. It is opaque, carrying no machine-readable account of its own reasoning. And it can return HTTP 200, with a well-formed body, containing an answer that is wrong or that politely declines to answer.

That last case is fatal to a status-code check. Your HTTP client sees a 200 and reports success. The user sees a request that did not do what they asked, which is a failure by the only definition that pays your invoice.

What Are the Seven Ways an AI Feature Fails in Production?

The seven classes are timeout, rate limit, provider overload, refusal, content filter rejection, malformed structured output, and truncated stream. Three arrive as HTTP errors; four arrive inside a successful HTTP 200, which is why a single catch block cannot see them.

Table of seven LLM error classes showing retry strategy, HTTP signals, and user-facing responses for AI error handling.

The seven AI failure classes, how each arrives on the wire, and what the user should be told.

Three deserve a closer look. A refusal is the model declining in-band: a successful delivery that does not do the job. Malformed structured output is the one readers least expect, because the call succeeded, the text arrived, and only your schema validator knows the shape is wrong. A truncated stream stopped early, and is more dangerous than a clean failure because the user may never notice.

How Does Each Provider Signal the Same Failure?

The same semantic failure arrives in four incompatible shapes, and a classifier that reads only one of them will silently pass the others through as successes.

Anthropic returns stop_reason: "refusal" inside a normal 200, alongside a stop_details object naming the policy category and a human-readable explanation. Both can be null, so branch on the stop reason rather than the presence of detail and keep fallback copy. A context reset is required before continuing; without one you get further refusals, per its guidance on handling LLM refusal responses.

OpenAI inverts the shape. finish_reason stays "stop" and a refusal field carries the wording on Chat Completions, while the Responses API delivers it as an output item of type: "refusal". Truncation arrives as finish_reason: "length" and policy blocks as finish_reason: "content_filter", per the OpenAI finish_reason reference.

Azure OpenAI takes a third route, rejecting a filtered prompt with an HTTP 400 carrying code: content_filter, per the Azure OpenAI content filtering reference.

Google Gemini uses finishReason on each candidate, where MAX_TOKENS means truncation and SAFETY, PROHIBITED_CONTENT, BLOCKLIST and SPII all mean a policy block, among others, per the Vertex AI finishReason reference. Gemini also adds RECITATION, a class no other provider has, where generation halts because the output reproduced training data too closely.

One semantic failure, four transport shapes.

Why Does Generic AI Error Handling Cost You Users?

A single toast collapses seven situations into one dead end. “Something went wrong” does not distinguish “wait ten seconds and this will work” from “this will never work with the request you wrote”. Users cannot tell which action helps, so many take the one that always does: they leave.

The larger cost is invisible in the error log. The generic handler destroys context on its way out, clearing the prompt and discarding partial output. This is where AI adoption quietly stalls. The feature demos well and pilots badly, and leadership concludes the model is not good enough, when the interface simply never told anyone what happened. We see the same misattribution with RAG hallucinations in production.

The advice to stay generic is not baseless. The Vercel AI SDK error handling guidance recommends exactly that, so server-side detail cannot leak to the client, and most published advice repeats it. That is sound as a rule about the error text. It is not a rule about the failure class: “the model declined” and “you are rate limited” leak nothing, and withholding them is what strands the user.

How Do You Build AI API Error Handling at the Boundary?

It needs a layer most codebases lack: a classifier between transport and interface. Its job is to turn everything a provider returns, from status codes to stop reasons to the body itself, into one semantic type.

Where the classifier sits. Provider signals resolve to a semantic type before either the retry policy or the UI sees them.

The boundary classifier in TypeScript. Status codes, stop reasons and body checks collapse into one discriminated union.

WHY THIS WORKS. Components never inspect a status code. They switch on a semantic failure type, so a provider migration touches this module and nothing in the UI. It forces the four in-band failures through the same pipeline as the three HTTP ones, where they can no longer pass as successes.

Abort handling is where this usually goes wrong first. A request killed by AbortSignal.timeout() rejects with TimeoutError, while a user pressing Stop rejects with AbortError from the same signal; collapsing both means retrying requests the user cancelled. Second, the Anthropic API error codes reference defines 529 as overloaded_error, but on a streaming connection it arrives as an error event after the 200.

How Should Each Failure Type Change What the User Sees?

With classification in place, the interface stops guessing. Four classes need bespoke treatment; the other three share one recoverable shell.

One error component. Four classes need bespoke treatment; the rest share a recoverable shell.

WHY THIS WORKS. Every branch keeps the user’s work recoverable, which is where perceived reliability actually comes from. The prompt stays editable, the partial answer stays on screen, and an unavoidable wait becomes visible rather than indefinite.

Truncation is worth building carefully: it is the only class where the correct response is to continue rather than start again. If you are already streaming AI responses in React with SSE, the partial text is in state and continuation is cheap. If not, the user reads an amputated answer as complete.

To find out which of the seven your own code handles, run the classifier fixtures against your provider and count the branches that never fire. Our AI development services team will walk that audit with you in a 30-minute call.

What AI Retry Strategy Fits Each Failure Class?

Retrying a refusal against the same model is a bug. A refusal is deterministic in the way that matters: the same input produces the same decline, at double the cost, while the user watches a spinner. Anthropic ships the alternative as tooling now: set fallbacks: "default" and the API reruns the declined request on a recommended model inside one call, with SDK middleware and sticky routing around it. Still a fallback rather than a retry, and the distinction decides how you bill and log it.

Retry policy by failure class. Four of seven are safely retryable; two never are.

When to use which. A workable AI retry strategy is per-class, not global: automatic retry belongs to timeouts, rate limits, provider overload, and schema failures. For a content filter rejection, only a changed request changes the outcome, and only the user can make it.

Underneath the per-class decision sit two policies that apply across classes. A circuit breaker stops you retrying into a provider that is already saturated. Trip it after a few consecutive overloads, then probe with a single request before you let traffic through again. A fallback path decides what happens once retries are exhausted, whether that is a smaller secondary model, a cached response, or the non-AI version of the feature. The breaker never rescues a refusal; a fallback can, which is precisely why the two are worth separating. Both matter more under load, as our work on multi-agent AI production scaling showed.

Do Hallucinations, Prompt Injection, and Agent Failures Belong Here?

Four things sit next to this taxonomy without belonging inside it, and conflating them is how this work becomes an unbounded project.

However, a hallucination is not a failure class in this sense. The transport succeeded, the schema validated, and the answer is confidently wrong. As a result, nothing in the response tells your handler to branch. Instead, evaluation and grounding move this number, while a classifier at the transport boundary has nothing to work with.

Security events are a different animal entirely. A prompt injection or an unsafe tool call is a security event, not a degraded response, so the correct behaviour is to halt rather than degrade. The OWASP Top 10 for LLM Applications ranks prompt injection first and recommends least-privilege tooling with human approval on high-risk actions. Route these to a kill switch, never to the error component.

What the seven-class taxonomy covers and what belongs to evaluation, security, or orchestration instead.

Anthropic distinguishes three refusal shapes and only two are catchable here: classifier declines carrying stop_reason: "refusal", validation failures arriving as 400 errors, and the model writing “I can’t help with that” as ordinary text under a normal stop reason. That third shape is indistinguishable from success at the boundary, so it belongs with hallucination.

Agents inherit all of this and then add to it. All seven classes apply to every model call an agent makes, but an agent adds failure modes a single-call taxonomy cannot see. Loops fail to terminate. Tool calls succeed with the wrong argument. State drifts across steps, and one that looked fine in isolation turns out to have poisoned the three after it. Those belong with AI agent reliability in production.

How Do You Monitor AI Feature Error Handling in Production?

Log the semantic failure class as a low-cardinality field on every AI call, alongside stop reason, time to first token and abort cause. Never persist prompt or response content. Per-class counts tell you which failures actually occur, and storing the prompts alongside them buys you very little for the retention risk it creates.

A taxonomy you do not measure is just a diagram. The OpenTelemetry GenAI semantic conventions take the same position: record a low-cardinality error.type on the span, and treat prompt capture as something you must be able to switch off.

There is a way to do everything above and still end up blind. A friendly on-screen message that hides the real failure from your logs leaves the user stuck and you none the wiser. Report accurately to monitoring whatever the screen says.

What Did This Change for AI Reliability?

Seven failure classes became individually handled instead of one, four of seven were identified as safely retryable, and user context survived every failure instead of being cleared. No model, prompt or retrieval change was involved.

Structural before-and-after across the engagement.

We cannot publish this client’s figures. Here instead is the arithmetic, which runs on your own logs in an afternoon and does not require trusting ours.

Multiply your monthly AI call volume by your observed failure rate, then by the share of those failures that are refusals or content filter rejections. That is how many calls your handler retries today that cannot succeed on any attempt. Multiply by your retry count and blended cost per call for the annual spend recovered by not retrying two of the seven classes. Every gain here came from the interface layer.

How Do You Roll Out LLM Error Handling in Your Own Stack?

Roll out in five steps: instrument for a week to learn your real failure distribution, write the classifier as a pure function with one fixture per class, branch the interface on the semantic type, split the retry policy, then verify each class by provoking it. Expect two to three weeks on a typical codebase.

Instrumentation comes first because guessing is how teams build an elaborate handler for a class they see twice a month. The fixture-per-class rule matters just as much: each of the seven should fail loudly if a provider changes its wire format. Sequenced this way, LLM error handling lands as one reviewable module rather than a diff across every component.

Finally, verify each class by provoking it deliberately: use a short AbortSignal.timeout() for timeouts, set a low max_tokens value for truncation, and use Anthropic’s documented magic test string, published on the same refusals page, to trigger a refusal. Most importantly, perform the negative check by firing a refusal and confirming that the client sends exactly one request.

Our web application development team runs this sequence with in-house engineers, or you can hire full-stack developers who have shipped it before.

Conclusion

This is an interface-layer project, not a model project. Name all seven classes, resolve them at one boundary, and give each a recoverable path.

Ultimately, the gap between an AI feature that demos well and one that survives production is rarely the model. Instead, it is the seven-to-one compression between what the provider reported and what the interface told the user.

If you want a second pair of eyes on your own failure paths, book an AI reliability review and we will tell you which of the seven classes your code is currently silent about. Our AI consulting services practice works with engineering teams across the US, UK, and India.

AI Feature Error Handling FAQ

Q. What is AI feature error handling?

It is the practice of classifying every way an AI endpoint can fail into a semantic type before the interface renders it, instead of branching on HTTP status codes. Four of the seven classes arrive inside a 200.

Q. What are the most common AI feature failures?

Timeout, rate limit, provider overload, refusal, content filter rejection, malformed structured output, and truncated stream. Three surface as HTTP errors; four arrive inside a 200.

Q. Should you retry a failed LLM request?

Sometimes. Retry timeouts, rate limits, provider overload, and schema failures with backoff behind a circuit breaker. Refusals are never retried against the same model, and content filter rejections never are at all.

Q. How do you handle AI errors in React or the Vercel AI SDK?

Put AI API error handling at the boundary, then switch one error component on the semantic failure type. Run the classifier in the AI SDK’s onError callback, and do not stop at a generic message.

Q. What is the difference between an AI error and an AI refusal?

A refusal arrives as HTTP 200 with valid output: the call worked, the model declined the task. Surface the provider’s explanation where there is one, your own copy where there is no

Exit mobile version