{"record":{"id":"62e360c0e78ace58","repo":"Hmbown/CodeWhale","slug":"postgrest-request-failed-http-res-status","errorCode":null,"errorMessage":"PostgREST request failed (HTTP ${res.status})","messagePattern":"PostgREST request failed \\(HTTP (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":466,"sourceCode":"  refuseUnderCi();\n  const url = process.env.SUPABASE_URL;\n  const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SECRET_KEY;\n  if (!url || !key) throw new Error(\"SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required\");\n  const endpoint = new URL(url);\n  if (endpoint.protocol !== \"https:\" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error(\"invalid Supabase endpoint\");\n  const res = await fetch(`${url.replace(/\\/$/, \"\")}/rest/v1/${path}`, {\n    method,\n    signal: AbortSignal.timeout(30_000),\n    redirect: \"error\",\n    headers: {\n      apikey: key,\n      Authorization: `Bearer ${key}`,\n      \"Content-Type\": \"application/json\",\n      ...(prefer ? { Prefer: prefer } : {}),\n    },\n    body: body === undefined ? undefined : JSON.stringify(body),\n  });\n  if (!res.ok) { await res.body?.cancel(); throw new Error(`PostgREST request failed (HTTP ${res.status})`); }\n  const text = await readBoundedResponse(res);\n  return text ? JSON.parse(text) : null;\n}\n\nexport async function readBoundedResponse(response, maxBytes = MAX_ENVELOPE_BYTES) {\n  const length = response.headers.get(\"content-length\");\n  if (length !== null && (!/^\\d+$/.test(length) || Number(length) > maxBytes)) {\n    await response.body?.cancel();\n    throw new Error(\"response exceeds size limit or has invalid length\");\n  }\n  if (!response.body) return \"\";\n  const reader = response.body.getReader();\n  const chunks = [];\n  let size = 0;\n  try {\n    while (true) {\n      const { value, done } = await reader.read();\n      if (done) break;","sourceCodeStart":448,"sourceCodeEnd":484,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L448-L484","documentation":"The publish script's postgrest() helper throws this whenever the Supabase/PostgREST HTTP response status is not ok (res.ok false). It cancels the body to free the connection and raises a generic error containing only the status code. Any 4xx/5xx from PostgREST (auth failure, bad table, RLS denial, constraint violation) surfaces here.","triggerScenarios":"Calling publish (POST facts_key/facts_release), emit-sql key lookup, or the channel existence GET when Supabase returns a non-2xx status: wrong SUPABASE_URL/table names, missing or expired service-role key, RLS policy blocking the anon key, or a 409 duplicate key_id.","commonSituations":"Publishing with an anon key instead of service role; facts_channel table missing or renamed; env vars (SUPABASE_URL/SUPABASE_KEY) unset or pointing at a different project; inserting a facts_key row that already exists without resolution=ignore-duplicates; network/proxy outage returning 502.","solutions":["Print res.status and the response body before throwing (temporarily log text via readBoundedResponse) to identify the PostgREST error detail","Verify SUPABASE_URL and service-role key env vars are set and belong to the same project","Run publish with --dry-run first to confirm channel and key setup before writing","Check the facts_channel row exists globally for the envelope's channel (error 689) and facts_key conflicts are handled by the Prefer: resolution=ignore-duplicates header","Check Supabase project status/logs for RLS or constraint errors"],"exampleFix":"// before\nif (!res.ok) { await res.body?.cancel(); throw new Error(`PostgREST request failed (HTTP ${res.status})`); }\n// after\nif (!res.ok) {\n  const detail = await res.text().catch(() => \"\");\n  await res.body?.cancel();\n  throw new Error(`PostgREST request failed (HTTP ${res.status}): ${detail.slice(0, 500)}`);\n}","handlingStrategy":"try-catch","validationCode":"if (!process.env.SUPABASE_URL || !process.env.SUPABASE_KEY) throw new Error('SUPABASE_URL/SUPABASE_KEY must be set');\nconst ping = await fetch(`${process.env.SUPABASE_URL}/rest/v1/facts_channel?select=id&limit=1`, { headers: { Authorization: `Bearer ${process.env.SUPABASE_KEY}` } });\nif (!ping.ok) throw new Error(`precheck failed: HTTP ${ping.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  await publish(envelope);\n} catch (e) {\n  if (String(e.message).includes('PostgREST request failed')) {\n    console.error('Supabase rejected the request; check env vars, RLS, table names, and run --dry-run first');\n  }\n  throw e;\n}","preventionTips":["Always validate with --dry-run before a real publish","Confirm service-role (not anon) credentials for write operations","Pin the expected table names and check Supabase schema migrations","Run from an environment with network access to the Supabase host"],"tags":["http","supabase","postgrest","network"],"backgroundTag":"http-error-response","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}