{"record":{"id":"1347b4d8191faa7f","repo":"koala73/worldmonitor","slug":"convex-embed-key-validation-unavailable-invalid-json","errorCode":null,"errorMessage":"Convex embed key validation unavailable: invalid-json","messagePattern":"Convex embed key validation unavailable: invalid-json","errorType":"exception","errorClass":"EmbedKeyUnavailableError","httpStatus":null,"severity":"error","filePath":"server/_shared/embed-key.ts","lineNumber":168,"sourceCode":"      },\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\n  if (!resp.ok) {\n    throw new EmbedKeyUnavailableError(\n      `Convex embed key validation unavailable: http-${resp.status}`,\n    );\n  }\n\n  let value: unknown;\n  try {\n    value = await resp.json();\n  } catch {\n    throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-json');\n  }\n\n  if (value === null) return null;\n  if (!isEmbedKeyResult(value)) {\n    throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-payload');\n  }\n  return value;\n}\n\n/**\n * Delete the Redis cache entry for a specific embed key hash.\n * Called after revocation so the key cannot be used during the TTL window.\n * Uses prefixed keys (no raw=true) matching the cache writes above.\n */\nexport async function invalidateEmbedKeyCache(keyHash: string): Promise<void> {\n  await deleteRedisKey(`${CACHE_KEY_PREFIX}${keyHash}`);\n}\n","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/_shared/embed-key.ts#L150-L186","documentation":"fetchFromConvex fetches embed-key validation data from a Convex endpoint and parses the HTTP response body as JSON. If resp.json() throws (body is not valid JSON — empty, HTML error page, truncated, or wrong content type), the function converts that low-level failure into EmbedKeyUnavailableError with reason 'invalid-json'. It signals the upstream Convex validation source is unreachable or malformed rather than that the key itself is invalid.","triggerScenarios":"The Convex HTTP endpoint returns a non-JSON body: a 502/504 HTML gateway error page, an empty body, a plain-text error, or a gzip/encoding mismatch that corrupts the body before parsing.","commonSituations":"Convex deployment URL misconfigured (pointing at a non-Convex host), Convex backend outage or deploy in progress, a proxy/CDN intercepting the request and returning an HTML error page, or network truncation.","solutions":["Log the raw response status and body text before json() to identify what the Convex endpoint actually returned","Verify the Convex deployment URL / environment config is correct and the backend is deployed and healthy","Retry with backoff — transient gateway errors commonly produce HTML bodies","Add a content-type check (application/json) and fail fast with a clearer message if it is not JSON","Treat EmbedKeyUnavailableError as 'validation unavailable' in callers and fall back to a cached or secondary validation path"],"exampleFix":"// before\nvalue = await resp.json();\n// after\nconst raw = await resp.text();\ntry { value = JSON.parse(raw); }\ncatch { throw new EmbedKeyUnavailableError(`Convex embed key validation unavailable: invalid-json (status=${resp.status}, body=${raw.slice(0, 120)})`); }","handlingStrategy":"try-catch","validationCode":"// Before the call: verify config\nif (!process.env.CONVEX_URL || !/^https:\\/\\/.+\\.convex\\.(site|cloud)/.test(process.env.CONVEX_URL)) {\n  throw new Error('CONVEX_URL is not a valid Convex deployment URL');\n}","typeGuard":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const key = await fetchFromConvex(hash);\n} catch (err) {\n  if (err instanceof EmbedKeyUnavailableError && err.message.includes('invalid-json')) {\n    // validation source unreachable: fall back to cached/secondary validation\n    logger.warn({ hash, err }, 'convex embed-key source returned non-JSON; using fallback');\n    return fallbackValidation(hash);\n  }\n  throw err;\n}","preventionTips":["Pin and health-check the Convex deployment URL; alert on non-2xx or HTML responses","Check resp.headers content-type === 'application/json' before parsing","Add retry with exponential backoff for transient gateway/5xx responses","Log raw body prefix on parse failure for diagnosis"],"tags":["network","json","convex","cache"],"backgroundTag":"invalid-json-response","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"}