{"record":{"id":"1b457b8761423f23","repo":"koala73/worldmonitor","slug":"convex-embed-key-validation-unavailable-missing-config","errorCode":null,"errorMessage":"Convex embed key validation unavailable: missing-config","messagePattern":"Convex embed key validation unavailable: missing-config","errorType":"exception","errorClass":"EmbedKeyUnavailableError","httpStatus":null,"severity":"error","filePath":"server/_shared/embed-key.ts","lineNumber":139,"sourceCode":"      console.warn(`[embed-key] discarding non-conforming validation payload (type=${Array.isArray(result) ? 'array' : typeof result})`);\n      return null;\n    }\n    return result;\n  } catch (err) {\n    // Transient Convex/network/config errors must stay retryable. Do not\n    // collapse them into null — that would return a misleading 401.\n    const unavailable = toUnavailableError(err);\n    console.warn('[embed-key] validateEmbedKey unavailable:', unavailable.message);\n    throw unavailable;\n  }\n}\n\n/** Fetch key validation from the Convex internal endpoint. */\nasync function fetchFromConvex(keyHash: string): Promise<EmbedKeyResult | null> {\n  const convexSiteUrl = process.env.CONVEX_SITE_URL;\n  const convexSharedSecret = process.env.CONVEX_SERVER_SHARED_SECRET;\n  if (!convexSiteUrl || !convexSharedSecret) {\n    throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: missing-config');\n  }\n\n  let resp: Response;\n  try {\n    resp = await fetch(`${convexSiteUrl}/api/internal-validate-embed-key`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'User-Agent': 'worldmonitor-gateway/1.0',\n        'x-convex-shared-secret': convexSharedSecret,\n      },\n      body: JSON.stringify({ keyHash }),\n      signal: AbortSignal.timeout(3_000),\n    });\n  } catch {\n    throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: fetch-error');\n  }\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/_shared/embed-key.ts#L121-L157","documentation":"fetchFromConvex validates embed keys by POSTing to a Convex internal HTTP endpoint. It throws EmbedKeyUnavailableError with `missing-config` when either CONVEX_SITE_URL or CONVEX_SERVER_SHARED_SECRET is unset in the environment, because key validation cannot even be attempted. This is an availability error (the validator is unreachable), not a verdict that the key is invalid.","triggerScenarios":"Any embed-key validation request where `process.env.CONVEX_SITE_URL` is empty/undefined OR `process.env.CONVEX_SERVER_SHARED_SECRET` is empty/undefined at the moment fetchFromConvex runs.","commonSituations":"Local development without the .env file loaded via loadEnvFile(); deploying the Railway worker or API without setting the two Convex variables; a typo'd env var name or a secret-scoping change that dropped the variables from one environment; CI running the server without secrets provisioned.","solutions":["Set both CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET in the environment (deployment dashboard or local .env loaded through loadEnvFile()).","Verify the exact variable names — no typos, no missing prefix — and that they are present in the environment of the process actually serving requests.","Restart/redeploy the service after adding the variables so the new environment is picked up.","Confirm the Convex deployment the URL points at has the internal-validate-embed-key endpoint and matching shared secret."],"exampleFix":"// before: server crashes validation\n// (no env vars set)\n\n// after (.env loaded via loadEnvFile())\nCONVEX_SITE_URL=https://your-deployment.convex.site\nCONVEX_SERVER_SHARED_SECRET=<secret>","handlingStrategy":"validation","validationCode":"if (!process.env.CONVEX_SITE_URL || !process.env.CONVEX_SERVER_SHARED_SECRET) {\n  throw new Error('Set CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET before serving embed-key requests');\n}","typeGuard":"function hasConvexConfig(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv &\n  { CONVEX_SITE_URL: string; CONVEX_SERVER_SHARED_SECRET: string } {\n  return typeof env.CONVEX_SITE_URL === 'string' && env.CONVEX_SITE_URL.length > 0 &&\n    typeof env.CONVEX_SERVER_SHARED_SECRET === 'string' && env.CONVEX_SERVER_SHARED_SECRET.length > 0;\n}","tryCatchPattern":"try {\n  const verdict = await result(keyHash);\n} catch (err) {\n  if (err instanceof EmbedKeyUnavailableError && err.message.endsWith('missing-config')) {\n    console.error('Convex embed-key env vars are not set; failing closed (treat key as unvalidated)');\n    return failClosed();\n  }\n  throw err;\n}","preventionTips":["Load local secrets through loadEnvFile() and never hand-roll env parsing.","Add a startup assertion that both CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET are present in server deployments.","Document both variables in deployment config so new environments provision them together.","Distinguish 'validator unavailable' from 'key invalid' in your embed-key handling so missing config fails closed, not open."],"tags":["environment","config","convex","embed-key"],"backgroundTag":"missing-env-var","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}