AI API calls fail in distinct ways: rate limits (429), network drops, content policy blocks, and model-level errors. When these surface as a blank message, a raw JSON dump, or a generic toast with no retry path, users blame the product — not the underlying API. Per iso-25010:2011 reliability.fault-tolerance, the system must degrade gracefully. A rate-limit error that tells the user to wait 60 seconds retains trust; a silent failure causes churn. Distinguishing at least two error types is the minimum required to make error messaging actionable rather than decorative.
High because undifferentiated error states leave users stranded — they cannot determine whether to retry, wait, rephrase, or escalate — directly degrading retention and support load.
Replace catch-all error handling with a typed error classifier that produces distinct messages for rate limits, network failures, and content policy blocks. Render the message inline in the conversation thread, not as a dismissible toast.
const getErrorMessage = (error: Error): string => {
if (error.message.includes('429') || error.message.includes('rate limit'))
return "Rate limit reached — wait a moment and try again."
if (error.message.includes('content_policy'))
return "Response blocked by content policy. Try rephrasing."
if (error.message.includes('fetch') || error.message.includes('network'))
return "Connection issue — check your internet and retry."
return "Generation failed. Try again or regenerate."
}
{error && (
<div className="flex gap-2 p-3 rounded-lg bg-destructive/10 text-destructive text-sm">
<AlertCircleIcon className="w-4 h-4 mt-0.5 shrink-0" />
<span>{getErrorMessage(error)} <button onClick={reload} className="underline">Retry</button></span>
</div>
)}
ID: ai-ux-patterns.transparency.error-state-handling
Severity: high
What to look for: Count all AI API call sites (streaming endpoints, completion calls, generation requests). For each, enumerate the error handling: try/catch blocks, .catch() handlers, or error state variables (error, isError, status === 'error'). Check whether errors are surfaced to the user with actionable messaging — not raw error objects dumped in the UI, but human-readable explanations. Enumerate the error types handled: rate limit (429), network, model, and content policy violations. At least 2 distinct error types should produce different messages. A generic catch-all "Something went wrong" with no retry guidance is NOT a pass — must not pass based on generic error handling alone.
Pass criteria: AI generation errors are caught and display a human-readable message in the conversation UI. Different error types produce at least 2 different messages. Report: "X of Y API call sites have error handling; Z distinct error types handled."
Fail criteria: AI errors result in a blank response, a raw JSON error dump, a generic "Something went wrong" toast, or a console-only error with nothing shown to the user.
Skip (N/A) when: Same as regeneration-button.
Detail on fail: "AI API errors caught in try/catch but UI shows only a generic toast with no action guidance. Rate limit errors and network errors are not distinguished.".
Remediation: Error messages are trust moments. A clear, helpful error message builds confidence even when things go wrong.
const getErrorMessage = (error: Error): string => {
if (error.message.includes('rate limit') || error.message.includes('429')) {
return "You've reached the rate limit. Please wait a moment and try again."
}
if (error.message.includes('network') || error.message.includes('fetch')) {
return "Connection issue — check your internet and try again."
}
if (error.message.includes('content_policy')) {
return "This response was blocked by the content policy. Try rephrasing your message."
}
return "Something went wrong generating the response. Try regenerating."
}
{error && (
<div className="flex items-start gap-3 p-3 rounded-lg bg-destructive/10 text-destructive text-sm">
<AlertCircleIcon className="w-4 h-4 mt-0.5 shrink-0" />
<div>
<p>{getErrorMessage(error)}</p>
<button onClick={reload} className="underline mt-1">Try again</button>
</div>
</div>
)}