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

  1. 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.
  2. Re-sync the publishable key secret after any key rotation.
  3. Verify the project's database migrations ran so facts_current exists with the selected columns.
  4. 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

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


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)