# AI error states are handled with clear user messaging

- **Pattern:** `ab-000342` (`ai-ux-patterns.transparency.error-state-handling`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000342
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000342)

## Why it matters

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.

## Severity rationale

High because undifferentiated error states leave users stranded — they cannot determine whether to retry, wait, rephrase, or escalate — directly degrading retention and support load.

## Remediation

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.

```tsx
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>
)}
```

## Detection

- **ID:** `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.

  ```tsx
  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>
  )}
  ```

---

## External references

- iso-25010 reliability.fault-tolerance

Taxons: user-experience, error-resilience

HTML version: https://auditbuffet.com/patterns/ab-000342
