Hmbown/CodeWhale · error · Error
supabase-http-${res.status}
supabase-http-${res.status}
Error message
supabase-http-${res.status} What it means
After fetching /rest/v1/facts_current, fetchCurrentRow requires a 2xx response. Any non-ok status is converted to `supabase-http-${res.status}` (after cancelling the body to free the connection), so callers can branch on the exact HTTP status embedded in the message — e.g. 401/403 for auth problems, 404 for a missing table, 5xx for Supabase outages.
Solutions
- Read the status in the message and fix accordingly: 401/403 → refresh SUPABASE_PUBLISHABLE_KEY; 404 → create/restore facts_current table; 429 → back off and respect rate limits.
- Re-sync the publishable key secret after any key rotation.
- Verify the project's database migrations ran so facts_current exists with the selected columns.
- Retry 5xx with backoff; treat 4xx as configuration problems that retrying will not fix.
Example fix
// before
const row = await fetchCurrentRow(channel, env);
// after
catch (e) {
const m = /supabase-http-(\d+)/.exec(e.message);
if (m && m[1].startsWith('5')) return retryWithBackoff();
if (m && m[1] === '429') return rateLimitedFallback();
} Defensive patterns
Strategy: retry
Validate before calling
// Cannot pre-validate an HTTP status; validate config instead: const ok = !!env.SUPABASE_PUBLISHABLE_KEY && /^https:\/\/[^/?#]+$/.test(env.SUPABASE_URL ?? '');
Try / catch
try {
return await fetchCurrentRow(channel, env);
} catch (e) {
const m = /supabase-http-(\d+)/.exec(e.message);
if (!m) throw e;
const status = Number(m[1]);
if (status === 429 || status >= 500) return retryWithBackoff(3);
throw new Error(`Supabase ${status}: check key/table`);
} Prevention
- Monitor Supabase status and set up alerts for 5xx spikes.
- Rotate keys via a documented process that updates worker secrets in the same change.
- Ensure DB migrations create facts_current before deploys that read it.
- Cache the last good row so 429/5xx can be served stale.
When it happens
Trigger: Supabase REST returns 401/403 (bad or revoked publishable key, missing apikey header), 404 (facts_current table absent), 429 (rate limit), or 5xx (platform error) to the built request.
Common situations: Rotated the anon key in the Supabase dashboard without updating the worker secret; RLS policies denying reads; facts_current table not yet created/migrated; Supabase incident producing 502/503.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- PostgREST request failed
- building bundle fetch client failed
- bundle fetch failed with HTTP status
- bundle fetch request failed
- bundle redirects may not change URL scheme
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/af8ed1a74cd5295a.
Report an issue: GitHub.
Appendix: source
Thrown at web/lib/cloud-facts.ts:269
export async function fetchCurrentRow(channel: string, env: CloudFactsEnv, opts: ResolveOptions = {}): Promise<FactsCurrentRow | null> {
if (!isValidChannel(channel)) throw new Error("invalid-channel");
const key = env.SUPABASE_PUBLISHABLE_KEY;
let base: URL;
try {
base = new URL(env.SUPABASE_URL ?? "");
if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash || !key || !isPublishableKey(key)) throw new Error();
} catch { throw new Error("supabase-not-configured"); }
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? SUPABASE_TIMEOUT_MS);
try {
const url = new URL(`${base.href.replace(/\/+$/, "")}/rest/v1/facts_current`);
url.search = new URLSearchParams({ channel: `eq.${channel}`, scope: "eq.global", select: "channel,release_id,facts_version,schema_version,envelope_version,applies_to,key_id,payload_b64,sig_b64,sigs,payload_sha256,published_at,not_after", limit: "1" }).toString();
const res = await (opts.fetchImpl ?? fetch)(url, {
headers: { apikey: key!, Authorization: `Bearer ${key}`, Accept: "application/json" },
signal: controller.signal,
redirect: "error",
});
if (!res.ok) { await res.body?.cancel(); throw new Error(`supabase-http-${res.status}`); }
const bytes = await readBoundedBody(res, MAX_ENVELOPE_BYTES);
const rows: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
if (!Array.isArray(rows) || rows.length > 1) throw new Error("supabase-bad-row");
if (rows.length === 0) return null;
if (!isObject(rows[0]) || !isEnvelope(envelopeFromRow(rows[0] as unknown as FactsCurrentRow))) throw new Error("supabase-bad-row");
return rows[0] as unknown as FactsCurrentRow;
} finally { clearTimeout(timer); }
}
async function kvGet(env: CloudFactsEnv, channel: string): Promise<unknown> {
if (!env.CURATED_KV) return null;
try {
const body = await env.CURATED_KV.get(`${KV_PREFIX}${channel}`, "stream");
if (!body) return null;
const bytes = await readBoundedBody({ body, headers: new Headers() }, MAX_ENVELOPE_BYTES);
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
} catch { return null; }
}View on GitHub (pinned to 433685b202)