{"record":{"id":"8cd3e4e7991d80ec","repo":"windmill-labs/windmill","slug":"what-supabaseerrormessage-body-res-statu","errorCode":null,"errorMessage":"${what}: ${supabaseErrorMessage(body) || res.statusText}","messagePattern":"\\$\\{what\\}: \\$\\{supabaseErrorMessage\\(body\\) \\|\\| res\\.statusText\\}","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/workspaceSettings/supabaseProvisioning.ts","lineNumber":73,"sourceCode":"\t{ code: 'eu-west-3', label: 'West EU (Paris)' },\n\t{ code: 'ap-southeast-1', label: 'Southeast Asia (Singapore)' },\n\t{ code: 'ap-northeast-1', label: 'Northeast Asia (Tokyo)' }\n]\n\nexport const DEFAULT_SUPABASE_REGION = 'eu-central-1'\n\nfunction headers(token: string): HeadersInit {\n\treturn { 'Content-Type': 'application/json', 'X-Supabase-Token': token }\n}\n\nasync function unwrap(res: Response, what: string): Promise<any> {\n\tif (!res.ok) {\n\t\t// Supabase access tokens are short-lived while `oauthStore` lasts as long as the tab, so\n\t\t// a stale one otherwise leaves every caller \"authorized\" and unable to reach the button\n\t\t// that would fix it. Forgetting it here is what puts Connect back on screen.\n\t\tif (res.status === 401) oauthStore.set(undefined)\n\t\tconst body = await res.text()\n\t\tthrow new Error(`${what}: ${supabaseErrorMessage(body) || res.statusText}`)\n\t}\n\treturn res.json()\n}\n\n/**\n * Supabase answers with `{ message }` or `{ error }` and occasionally plain text.\n * Surfacing the raw body puts a JSON blob in front of the user, so unwrap it to\n * the sentence inside.\n */\nexport function supabaseErrorMessage(body: string): string {\n\ttry {\n\t\tconst parsed = JSON.parse(body)\n\t\treturn parsed?.message ?? parsed?.error ?? parsed?.msg ?? body\n\t} catch {\n\t\treturn body\n\t}\n}\n","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/workspaceSettings/supabaseProvisioning.ts#L55-L91","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!oauthStoreValue) { showConnectScreen(); return }\nconst testRes = await fetch(`${base}/api/oauth/supabase_orgs`, { headers: headers(token) })\nif (testRes.status === 401) { oauthStore.set(undefined); showConnectScreen(); return }","typeGuard":"function isSupabaseErrorBody(b: unknown): b is { message?: string; error?: string } {\n  return typeof b === 'object' && b !== null && ('message' in b || 'error' in b)\n}","tryCatchPattern":"try {\n  const orgs = await listSupabaseOrgs(token)\n} catch (e) {\n  if (/401|unauthorized/i.test(e.message)) promptReconnect()\n  else if (/429/.test(e.message)) scheduleRetry()\n  else showError(e.message)\n}","preventionTips":["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."],"tags":["http","oauth","supabase","api-error"],"backgroundTag":"supabase-api-request-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}