{"record":{"id":"5be453fce8c03b59","repo":"koala73/worldmonitor","slug":"label-http-status-billingcode","errorCode":null,"errorMessage":"${label} HTTP ${status} (${billingCode})","messagePattern":"(.+?) HTTP (.+?) \\((.+?)\\)","errorType":"exception","errorClass":"BillingDenialError","httpStatus":null,"severity":"error","filePath":"api/mcp/billing-denial.ts","lineNumber":107,"sourceCode":"    this.status = 400;\n    this.violations = violations;\n  }\n}\n\n/**\n * Throws BillingDenialError when a non-ok gateway response carries the\n * billing-verification marker header. Detection is header-only, so callers\n * that read the error body for detail can still consume it afterwards.\n */\nexport function throwIfBillingDenial(response: ToolFetchResponse, label: string): void {\n  if (response.ok) return;\n  const marker = response.headers?.get('X-Billing-Verification');\n  if (!marker || !BILLING_VERIFICATION_CODES.has(marker)) return;\n  // Distinguish a missing header from a present-but-zero value: Number(null)\n  // is 0 (finite), which would silently masquerade as an explicit 0s hint.\n  const retryHeader = response.headers?.get('Retry-After');\n  const rawRetryAfter = retryHeader == null ? Number.NaN : Number(retryHeader);\n  throw new BillingDenialError(\n    label,\n    response.status,\n    marker as BillingVerificationCode,\n    Number.isFinite(rawRetryAfter) ? rawRetryAfter : undefined,\n  );\n}\n\nfunction sanitizeViolationField(value: unknown): string | null {\n  if (typeof value !== 'string') return null;\n  const field = value.trim().slice(0, MAX_VIOLATION_FIELD_LEN);\n  return SAFE_VIOLATION_FIELD.test(field) ? field : null;\n}\n\nfunction sanitizeViolationDescription(value: unknown): string | null {\n  if (typeof value !== 'string') return null;\n  const description = value.replace(/\\s+/g, ' ').trim().slice(0, MAX_VIOLATION_DESCRIPTION_LEN);\n  if (!description || UNSAFE_VIOLATION_DESCRIPTION.test(description)) return null;\n  return description;","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/koala73/worldmonitor/blob/a96956387a927b8cd7aa34b0c41fca357e746be9/api/mcp/billing-denial.ts#L89-L125","documentation":"claimProActivationPresentation() atomically re-checks server-side activation state and reserves one markerless presentation across devices; it needs both getConvexClient() and getConvexApi(). If either resolves null it throws 'Convex unavailable' before requireCurrentConvexUser or the payments.billing.claimProActivationPresentation mutation run. Null clients come from a missing VITE_CONVEX_URL or a ConvexClient constructor failure (Firefox 149/Linux degrades to null by design).","triggerScenarios":"App built without VITE_CONVEX_URL; browser-side ConvexClient constructor rejection; client init failed mid-boot when the user attempts to claim Pro activation.","commonSituations":"Fresh clone without .env.local; staging/preview build missing env; browser-specific interop bug; tests without a Convex client factory.","solutions":["Set VITE_CONVEX_URL to the Convex deployment and rebuild","Check the console for '[convex-client] ConvexClient constructor rejected:'","Try Chrome to rule out the Firefox 149/Linux constructor bug","Defer the activation-claim UI behind a client-availability check and show an env banner when null"],"exampleFix":"// before\nconst outcome = await claimProActivationPresentation(activationKey, claimNonce);\n// 'Convex unavailable'\n\n// after\nif (!(await getConvexClient())) {\n  showEnvBanner('Convex is not configured; Pro activation cannot be claimed yet.');\n  return;\n}\nconst outcome = await claimProActivationPresentation(activationKey, claimNonce);","handlingStrategy":"validation","validationCode":"if (!(await getConvexClient())) {\n  showEnvBanner('Convex is not configured; Pro activation cannot be claimed yet.');\n  return;\n}\nawait claimProActivationPresentation(activationKey, claimNonce);","typeGuard":null,"tryCatchPattern":"try {\n  const outcome = await claimProActivationPresentation(activationKey, claimNonce);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Convex unavailable') showEnvBanner('Backend not configured.');\n  else throw e;\n}","preventionTips":["Check Convex client availability before rendering activation-claim UI","Ensure VITE_CONVEX_URL is set in every build that can show Pro activation","Watch for the constructor-rejection console warning on Firefox 149/Linux"],"tags":["convex","env-var","client-init","billing","pro-activation"],"backgroundTag":"database-client-unavailable","analyzedSha":"a96956387a927b8cd7aa34b0c41fca357e746be9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}