windmill-labs/windmill · error
${what}: ${supabaseErrorMessage(body) || res.statusText}
Error message
${what}: ${supabaseErrorMessage(body) || res.statusText} What it means
unwrap is the shared response checker for Supabase Management API calls made during workspace provisioning. Any non-OK HTTP response is thrown as `<what>: <supabase-provided message or statusText>`. A 401 additionally clears the stored OAuth token so the Connect screen reappears. It surfaces the upstream Supabase error to the user with context about which call failed.
Source
Thrown at frontend/src/lib/components/workspaceSettings/supabaseProvisioning.ts:73
{ code: 'eu-west-3', label: 'West EU (Paris)' },
{ code: 'ap-southeast-1', label: 'Southeast Asia (Singapore)' },
{ code: 'ap-northeast-1', label: 'Northeast Asia (Tokyo)' }
]
export const DEFAULT_SUPABASE_REGION = 'eu-central-1'
function headers(token: string): HeadersInit {
return { 'Content-Type': 'application/json', 'X-Supabase-Token': token }
}
async function unwrap(res: Response, what: string): Promise<any> {
if (!res.ok) {
// Supabase access tokens are short-lived while `oauthStore` lasts as long as the tab, so
// a stale one otherwise leaves every caller "authorized" and unable to reach the button
// that would fix it. Forgetting it here is what puts Connect back on screen.
if (res.status === 401) oauthStore.set(undefined)
const body = await res.text()
throw new Error(`${what}: ${supabaseErrorMessage(body) || res.statusText}`)
}
return res.json()
}
/**
* Supabase answers with `{ message }` or `{ error }` and occasionally plain text.
* Surfacing the raw body puts a JSON blob in front of the user, so unwrap it to
* the sentence inside.
*/
export function supabaseErrorMessage(body: string): string {
try {
const parsed = JSON.parse(body)
return parsed?.message ?? parsed?.error ?? parsed?.msg ?? body
} catch {
return body
}
}
View on GitHub (pinned to e474e8803c)
Solutions
- If the message says unauthorized (401), click Connect again to re-authenticate — the stale token was already cleared.
- Generate a fresh Supabase access token with the required scopes (org read/write, project create) and reconnect.
- Retry after a delay if the status is 429 rate limit.
- Check status.supabase.com if the error is 5xx, then retry.
- Verify the Supabase account actually has an organization (and quota) before provisioning.
Defensive patterns
Strategy: try-catch
Validate before calling
if (!oauthStoreValue) { showConnectScreen(); return }
const testRes = await fetch(`${base}/api/oauth/supabase_orgs`, { headers: headers(token) })
if (testRes.status === 401) { oauthStore.set(undefined); showConnectScreen(); return } Type guard
function isSupabaseErrorBody(b: unknown): b is { message?: string; error?: string } {
return typeof b === 'object' && b !== null && ('message' in b || 'error' in b)
} Try / catch
try {
const orgs = await listSupabaseOrgs(token)
} catch (e) {
if (/401|unauthorized/i.test(e.message)) promptReconnect()
else if (/429/.test(e.message)) scheduleRetry()
else showError(e.message)
} Prevention
- Regenerate Supabase access tokens regularly and reconnect before long provisioning sessions.
- Create tokens with the full org/project management scopes.
- Check Supabase status page before large provisioning runs.
When it happens
Trigger: Any call routed through unwrap (listSupabaseOrgs, listSupabaseProjects, createSupabaseProject, pooler configs fetch) when Supabase answers 401 (expired/revoked access token), 403 (no org permission), 404, 429 (rate limit), or 5xx.
Common situations: Expired Supabase personal access token in oauthStore after sitting on the page; token created without the org/project scopes needed; Supabase rate limiting org creation; Supabase API outage.
Related errors
- Failed to complete GitHub app installation
- ApiError with mapped HTTP status message (e.g. "Not Found",
- Generic Error: status: ${errorStatus}; status text: ${errorS
- body.error || res.statusText
- Failed to create flow ${remotePath}: ${e.body ?? e.message}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/8cd3e4e7991d80ec.
Report an issue: GitHub.