{"record":{"id":"e19dee95b5a12ec2","repo":"koala73/worldmonitor","slug":"convex-embed-key-validation-unavailable-invalid-payload","errorCode":null,"errorMessage":"Convex embed key validation unavailable: invalid-payload","messagePattern":"Convex embed key validation unavailable: invalid-payload","errorType":"exception","errorClass":"EmbedKeyUnavailableError","httpStatus":null,"severity":"error","filePath":"server/_shared/embed-key.ts","lineNumber":173,"sourceCode":"    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":155,"sourceCodeEnd":186,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/_shared/embed-key.ts#L155-L186","documentation":"After successfully parsing the Convex response body as JSON, fetchFromConvex validates its shape with isEmbedKeyResult. If the parsed value is a JSON document but does not match the expected embed-key result schema, this EmbedKeyUnavailableError with reason 'invalid-payload' is thrown. It means the Convex function responded but returned an unexpected structure — usually a contract drift between the Convex query and this client.","triggerScenarios":"The Convex query was changed or renamed and now returns a different object shape; a deploy returned an error object like {error: ...} with HTTP 200; a proxy returned JSON (e.g. {message:'unauthorized'}) instead of the expected result; the Convex function returns null wrapped differently or omits required fields.","commonSituations":"Version skew after a Convex deploy (server updated, API worker not, or vice versa); environment pointing at the wrong Convex deployment (staging vs production schema); auth/proxy layer injecting its own JSON error body.","solutions":["Inspect the actual payload from the Convex endpoint and diff it against isEmbedKeyResult's expected fields","Ensure the Convex query and the server/_shared/embed-key.ts contract are deployed in lockstep","Verify the Convex deployment URL points at the intended environment (staging vs production)","Check for an auth or proxy layer returning its own JSON error body with HTTP 200","Broaden the error message to include the unexpected keys for faster diagnosis"],"exampleFix":"// before\nif (!isEmbedKeyResult(value)) {\n  throw new EmbedKeyUnavailableError('Convex embed key validation unavailable: invalid-payload');\n}\n// after\nif (!isEmbedKeyResult(value)) {\n  throw new EmbedKeyUnavailableError(`Convex embed key validation unavailable: invalid-payload ${JSON.stringify(value).slice(0, 200)}`);\n}","handlingStrategy":"type-guard","validationCode":"// Before the call: assert deployment/contract version\nconst res = await fetch(`${CONVEX_URL}/api/query`, { method: 'POST' });\nif (res.status !== 200) throw new Error(`Convex returned ${res.status}; deploy is unhealthy`);","typeGuard":"function isEmbedKeyResult(v: unknown): v is { key: string; valid: boolean } {\n  return typeof v === 'object' && v !== null\n    && 'key' in v && 'valid' in v\n    && typeof (v as any).key === 'string'\n    && typeof (v as any).valid === 'boolean';\n}","tryCatchPattern":"try {\n  const result = await fetchFromConvex(hash);\n} catch (err) {\n  if (err instanceof EmbedKeyUnavailableError && err.message.includes('invalid-payload')) {\n    logger.error({ hash, err }, 'Convex payload contract drift; check deploy versions');\n    return null; // or fall back to local validation\n  }\n  throw err;\n}","preventionTips":["Keep the Convex query schema and embed-key.ts types in the same repo and deploy them together","Add a contract test that runs isEmbedKeyResult against a live/staged Convex response","Point staging workers at staging Convex deployments, production at production","Version the Convex function response shape when it changes"],"tags":["schema","convex","contract-drift","validation"],"backgroundTag":"unexpected-response-shape","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"}