{"record":{"id":"28688d33aff0e25f","repo":"Hmbown/CodeWhale","slug":"supabase-url-and-supabase-service-role-key-are-required","errorCode":null,"errorMessage":"SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required","messagePattern":"SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":451,"sourceCode":"    `insert into public.facts_key (key_id, scope, algorithm, public_key, status)`,\n    `  values (${sqlLiteral(envelope.key_id)}, 'global', 'ed25519', ${sqlLiteral(publicKeyB64)}, 'active')`,\n    `  on conflict (key_id) do nothing;`,\n    `insert into public.facts_release (channel_id, facts_version, schema_version, envelope_version, applies_to, key_id, payload_b64, sig_b64, sigs, payload, published_at, not_after, published_by, notes)`,\n    `  select c.id, ${envelope.facts_version}, ${envelope.schema_version}, ${envelope.envelope}, ${sqlLiteral(envelope.applies_to)}, ${sqlLiteral(envelope.key_id)},`,\n    `         ${sqlLiteral(envelope.payload_b64)}, ${sqlLiteral(envelope.sig_b64)}, ${sqlLiteral(JSON.stringify(envelope.sigs ?? []))}::jsonb,`,\n    `         ${sqlLiteral(payloadJson)}::jsonb, ${sqlLiteral(envelope.published_at)}::timestamptz, ${sqlLiteral(check.payload.not_after ?? null)}::timestamptz,`,\n    `         ${sqlLiteral(publishedBy)}, ${sqlLiteral(notes)}`,\n    `    from public.facts_channel c where c.scope = 'global' and c.slug = ${sqlLiteral(envelope.channel)};`,\n    \"commit;\",\n    \"\",\n  ].join(\"\\n\");\n}\n\nasync function postgrest(path, { method = \"GET\", body, prefer } = {}) {\n  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}","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L433-L469","documentation":"postgrest performs the REST calls to Supabase and requires both SUPABASE_URL and a service-role key (SUPABASE_SERVICE_ROLE_KEY, falling back to SUPABASE_SECRET_KEY) in the environment. Without them it throws before any network call, since anonymous publication to the facts tables is impossible.","triggerScenarios":"Calling any publishing flow that reaches postgrest when SUPABASE_URL is unset or empty, or when neither SUPABASE_SERVICE_ROLE_KEY nor SUPABASE_SECRET_KEY is set — e.g. env file not loaded, wrong shell profile, or the variable exported under a different name.","commonSituations":"Running the publish script outside the founder's shell that sources the secrets; .env not exported (dotenv not invoked); key renamed after a Supabase plan/SDK change (newer SUPABASE_SECRET_KEY naming is supported but the legacy var is what most setups set).","solutions":["Export both variables before running: SUPABASE_URL=https://<project>.supabase.co and SUPABASE_SERVICE_ROLE_KEY=<service-role-key> (or SUPABASE_SECRET_KEY)","Source the secrets file (e.g. `set -a; . ./.env.secrets; set +a`) or pass them inline for the single command","Verify with `echo \"${SUPABASE_URL:?}\" ${SUPABASE_SERVICE_ROLE_KEY:+set}` that both are present before invoking the script"],"exampleFix":"// before\nnode web/scripts/facts-publish.mjs ...\n// after\nSUPABASE_URL=https://xyz.supabase.co SUPABASE_SERVICE_ROLE_KEY=eyJ... node web/scripts/facts-publish.mjs ...","handlingStrategy":"validation","validationCode":"if (!process.env.SUPABASE_URL) throw new Error(\"set SUPABASE_URL (https://<project>.supabase.co) before publishing\");\nif (!process.env.SUPABASE_SERVICE_ROLE_KEY && !process.env.SUPABASE_SECRET_KEY) throw new Error(\"set SUPABASE_SERVICE_ROLE_KEY (or SUPABASE_SECRET_KEY) before publishing\");","typeGuard":"function hasSupabaseEnv(env = process.env) {\n  return Boolean(env.SUPABASE_URL) && Boolean(env.SUPABASE_SERVICE_ROLE_KEY || env.SUPABASE_SECRET_KEY);\n}","tryCatchPattern":"try {\n  await publishFacts(envelope);\n} catch (err) {\n  if (err.message === \"SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required\") {\n    console.error(\"Export SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY (source your secrets file) and retry\");\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Keep a secrets file sourced by the publish wrapper (set -a; . ./.env.secrets; set +a)","Pre-flight check both env vars in the script entry point before doing expensive signing work","Document the exact variable names (note the SUPABASE_SECRET_KEY fallback) where operators will find them"],"tags":["environment","supabase","configuration"],"backgroundTag":"missing-env-var","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"}