Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Error display and troubleshooting pattern for Supabase Studio. Use when showing a failed API request or query error in the UI (AlertError, toast, inline message), adding troubleshooting steps for a new error type, or wiring up the AI assistant debug button from an error state.
.claude/skills/supabase-studio-error-handling/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -15% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -7% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 11% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -2% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 74% | 0% |
Full docs and code examples: apps/studio/components/interfaces/ErrorHandling/README.md
Classification happens in the data layer: handleError in data/fetchers.ts tests the error message against ERROR_PATTERNS and throws the matching error subclass (e.g. ConnectionTimeoutError extends ResponseError). The component (ErrorMatcher) reads errorType from the instance and does an O(1) lookup — it never does regex matching.
handleError() → throws ConnectionTimeoutError → React Query catches → ErrorMatcher reads errorType → renders troubleshooting| File | Purpose | | ------------------------------------- | ---------------------------------------------------------------- | | data/error-patterns.ts | Array of { pattern, ErrorClass } — the regex lives here | | types/api-errors.ts | Error classes, KnownErrorType union, ClassifiedError type | | ErrorMatcher.tsx | Component — reads errorType, looks up mapping, renders | | error-mappings.tsx | Record<KnownErrorType, { id, Troubleshooting: ComponentType }> | | errorMappings/ConnectionTimeout.tsx | Reference troubleshooting component | | TroubleshootingSections.tsx | Reusable accordion section components | | TroubleshootingAccordion.tsx | Accordion wrapper with telemetry |
| Situation | Use | | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | A query failed and the page/section can't render its data (the default case — most of Studio) | AlertError from components/ui/AlertError | | The error may be a classified type with its own troubleshooting steps (e.g. connection timeout) | ErrorMatcher from components/interfaces/ErrorHandling/ErrorMatcher — pass a fallback for the unclassified case | | A mutation failed | The mutation hook's default onError toast (toast.error from sonner) — don't render an alert (see studio-queries) |
AlertError (default)Renders a warning Admonition with the error message, generic "try refreshing / contact support" instructions, and a Contact support button pre-filled with projectRef, subject, and the error message.
tsxif (isError) return <AlertError error={error} subject="Failed to retrieve invoices" />
subject is the human-readable title, phrased Failed to <verb> <thing>. Pass projectRef when in a project context so the support form is pre-filled.error is the React Query error object (anything with message); 503 responses are reworded automatically.additionalActions for a retry or navigate button; hideContactSupport only when support genuinely can't help (e.g. a user-input error).{isError && <AlertError … />} for secondary panels that shouldn't block the rest of the page.ErrorMatcher (classified errors)Use when the data layer may have classified the error into a KnownErrorType with dedicated troubleshooting UI. It reads errorType from the error instance and renders the mapped Troubleshooting component, or fallback when there is no mapping. Today this is wired for the table editor sidebar; reach for it when adding troubleshooting for a new error type rather than as a general replacement for AlertError.
ErrorMatcher usagePass the full error object from React Query — not error.message:
tsx{ isError && ( <ErrorMatcher title="Failed to load tables" error={error} supportFormParams={{ projectRef }} /> ) }
error.message to ErrorMatcher — pass the full error object so the class is preserved.error-mappings.tsx — they belong in data/error-patterns.ts.Object.assign to stamp errorType — throw a proper subclass instead.supportFormParams={{ projectRef }}.<ErrorMatcher> caller.onDebugWithAI, onRestartProject) to troubleshooting components — use hooks inside them instead.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 14,621 | 7,482 | -49% | 1 | 1 | 0% | 2,715 | 2,298 | -15% | 0 | 0 | — |
case-02 | fail→pass | 17,130 | 8,226 | -52% | 1 | 1 | 0% | 2,804 | 2,614 | -7% | 0 | 0 | — |
case-03 | fail→pass | 16,001 | 9,515 | -41% | 1 | 1 | 0% | 2,415 | 2,687 | +11% | 0 | 0 | — |
case-04 | fail→pass | 13,466 | 6,357 | -53% | 1 | 1 | 0% | 2,069 | 2,022 | -2% | 0 | 0 | — |
case-05 | fail→pass | 6,443 | 4,477 | -31% | 1 | 1 | 0% | 983 | 1,712 | +74% | 0 | 0 | — |
case-06 | fail→pass | 13,825 | 5,405 | -61% | 1 | 1 | 0% | 2,118 | 1,986 | -6% | 0 | 0 | — |
case-07 | fail→pass | 11,820 | 4,371 | -63% | 1 | 1 | 0% | 1,850 | 1,777 | -4% | 0 | 0 | — |
case-08 | fail→pass | 17,276 | 5,130 | -70% | 1 | 1 | 0% | 2,633 | 1,823 | -31% | 0 | 0 | — |
case-09 | fail→pass | 12,794 | 4,112 | -68% | 1 | 1 | 0% | 1,909 | 1,726 | -10% | 0 | 0 | — |
case-10 | pass→pass | 16,601 | 4,472 | -73% | 1 | 1 | 0% | 2,448 | 1,669 | -32% | 0 | 0 | — |
case-11 | pass→fail | 8,034 | 4,763 | -41% | 1 | 1 | 0% | 1,179 | 1,665 | +41% | 0 | 0 | — |
case-12 | fail→pass | 15,806 | 4,111 | -74% | 1 | 1 | 0% | 2,223 | 1,587 | -29% | 0 | 0 | — |
case-13 | fail→pass | 12,436 | 4,463 | -64% | 1 | 1 | 0% | 2,110 | 1,729 | -18% | 0 | 0 | — |
case-14 | fail→pass | 11,230 | 5,528 | -51% | 1 | 1 | 0% | 1,790 | 1,891 | +6% | 0 | 0 | — |
case-15 | fail→pass | 14,049 | 4,533 | -68% | 1 | 1 | 0% | 2,495 | 1,828 | -27% | 0 | 0 | — |
case-16 | fail→pass | 16,881 | 7,942 | -53% | 1 | 1 | 0% | 2,516 | 2,175 | -14% | 0 | 0 | — |
case-17 | fail→pass | 14,777 | 3,672 | -75% | 1 | 1 | 0% | 2,233 | 1,574 | -30% | 0 | 0 | — |
case-18 | fail→pass | 14,860 | 6,763 | -54% | 1 | 1 | 0% | 2,500 | 2,094 | -16% | 0 | 0 | — |
case-19 | pass→pass | 13,592 | 11,843 | -13% | 1 | 1 | 0% | 2,395 | 3,381 | +41% | 0 | 0 | — |
case-20 | pass→pass | 10,691 | 18,185 | +70% | 1 | 1 | 0% | 1,882 | 3,071 | +63% | 0 | 0 | — |
case-21 | pass→pass | 14,828 | 12,418 | -16% | 1 | 1 | 0% | 2,476 | 3,206 | +29% | 0 | 0 | — |
case-22 | pass→pass | 12,401 | 9,995 | -19% | 1 | 1 | 0% | 2,115 | 2,772 | +31% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +68 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.