koala73/worldmonitor · error · BillingDenialError

${label} HTTP ${status} (${billingCode})

Error message

${label} HTTP ${status} (${billingCode})

What it means

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).

Source

Thrown at api/mcp/billing-denial.ts:107

    this.status = 400;
    this.violations = violations;
  }
}

/**
 * Throws BillingDenialError when a non-ok gateway response carries the
 * billing-verification marker header. Detection is header-only, so callers
 * that read the error body for detail can still consume it afterwards.
 */
export function throwIfBillingDenial(response: ToolFetchResponse, label: string): void {
  if (response.ok) return;
  const marker = response.headers?.get('X-Billing-Verification');
  if (!marker || !BILLING_VERIFICATION_CODES.has(marker)) return;
  // Distinguish a missing header from a present-but-zero value: Number(null)
  // is 0 (finite), which would silently masquerade as an explicit 0s hint.
  const retryHeader = response.headers?.get('Retry-After');
  const rawRetryAfter = retryHeader == null ? Number.NaN : Number(retryHeader);
  throw new BillingDenialError(
    label,
    response.status,
    marker as BillingVerificationCode,
    Number.isFinite(rawRetryAfter) ? rawRetryAfter : undefined,
  );
}

function sanitizeViolationField(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const field = value.trim().slice(0, MAX_VIOLATION_FIELD_LEN);
  return SAFE_VIOLATION_FIELD.test(field) ? field : null;
}

function sanitizeViolationDescription(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const description = value.replace(/\s+/g, ' ').trim().slice(0, MAX_VIOLATION_DESCRIPTION_LEN);
  if (!description || UNSAFE_VIOLATION_DESCRIPTION.test(description)) return null;
  return description;

View on GitHub (pinned to a96956387a)

Solutions

  1. Set VITE_CONVEX_URL to the Convex deployment and rebuild
  2. Check the console for '[convex-client] ConvexClient constructor rejected:'
  3. Try Chrome to rule out the Firefox 149/Linux constructor bug
  4. Defer the activation-claim UI behind a client-availability check and show an env banner when null

Example fix

// before
const outcome = await claimProActivationPresentation(activationKey, claimNonce);
// 'Convex unavailable'

// after
if (!(await getConvexClient())) {
  showEnvBanner('Convex is not configured; Pro activation cannot be claimed yet.');
  return;
}
const outcome = await claimProActivationPresentation(activationKey, claimNonce);
Defensive patterns

Strategy: validation

Validate before calling

if (!(await getConvexClient())) {
  showEnvBanner('Convex is not configured; Pro activation cannot be claimed yet.');
  return;
}
await claimProActivationPresentation(activationKey, claimNonce);

Try / catch

try {
  const outcome = await claimProActivationPresentation(activationKey, claimNonce);
} catch (e) {
  if (e instanceof Error && e.message === 'Convex unavailable') showEnvBanner('Backend not configured.');
  else throw e;
}

Prevention

When it happens

Trigger: App built without VITE_CONVEX_URL; browser-side ConvexClient constructor rejection; client init failed mid-boot when the user attempts to claim Pro activation.

Common situations: Fresh clone without .env.local; staging/preview build missing env; browser-specific interop bug; tests without a Convex client factory.

Related errors


AI-assisted analysis of koala73/worldmonitor@a96956387a (2026-08-21). Data as JSON: /api/errors/5be453fce8c03b59. Report an issue: GitHub.