koala73/worldmonitor · warning

buildAuthHeaders: free-tier context has no credentials — a f

Error message

buildAuthHeaders: free-tier context has no credentials — a free-tier tool must not call a credentialed downstream

What it means

requireSignedInUserId(action) is the billing service's shared guard: it reads getCurrentClerkUser()?.id and throws `Sign in to ${action}.` (e.g., 'Sign in to claim Pro activation.') when no Clerk session exists. The action string names the exact billing operation, so the message tells the developer which gated flow fired. No billing mutation or Convex call is attempted.

Source

Thrown at api/mcp/auth.ts:137

  url: string,
  body: BodyInit | null | undefined,
): Promise<Record<string, string>> {
  if (context.kind === 'env_key' || context.kind === 'user_key') {
    // user_key (#4859): the downstream REST gateway validates the raw key
    // itself (Convex hash lookup + the #4611 apiAccess gate + per-account
    // limits), so usage attributes to the key owner exactly like a direct
    // REST call — no internal-HMAC identity smuggling needed.
    return { 'X-WorldMonitor-Key': context.apiKey };
  }
  if (context.kind === 'free') {
    // U7: a free-tier context has no principal to authenticate as, so there is
    // nothing honest to sign. Throwing is the fail-closed choice — the
    // alternative (falling through to the `pro` HMAC branch below) would mint
    // an internally-trusted signature for an anonymous caller, which is the
    // one outcome the free tier must never produce. A free-tier tool that
    // reaches here is misconfigured: it declared `_freeTier` while calling a
    // credentialed downstream.
    throw new Error('buildAuthHeaders: free-tier context has no credentials — a free-tier tool must not call a credentialed downstream');
  }
  // context.kind === 'pro'
  const secret = process.env.MCP_INTERNAL_HMAC_SECRET ?? '';
  if (!secret) {
    // Should never happen in production (deploy gate at U10) — surface as
    // an error so the tool fetch fails fast rather than silently 401-ing
    // at the gateway with a confusing "invalid_internal_mcp_signature".
    throw new Error('MCP_INTERNAL_HMAC_SECRET not configured');
  }
  const signed = await signInternalMcpRequest({
    method,
    url,
    body,
    userId: context.userId,
    secret,
  });
  return buildInternalMcpHeaders(signed);
}

View on GitHub (pinned to a96956387a)

Solutions

  1. Sign in and retry the billing action
  2. Ensure Clerk is fully loaded before invoking billing flows
  3. Gate billing buttons on auth state so the call never fires signed-out
  4. Check ClerkProvider/publishable key configuration if the user should be signed in

Example fix

// before
const status = await claimProActivationPresentation(activationKey, claimNonce);
// 'Sign in to claim Pro activation.'

// after
if (!getCurrentClerkUser()?.id) {
  openSignIn();
  return;
}
const status = await claimProActivationPresentation(activationKey, claimNonce);
Defensive patterns

Strategy: validation

Validate before calling

if (!getCurrentClerkUser()?.id) { openSignIn(); return; }
const outcome = await claimProActivationPresentation(activationKey, claimNonce);

Type guard

const hasActiveClerkUser = (u: { id: string } | null | undefined): u is { id: string } =>
  typeof u?.id === 'string' && u.id.length > 0;

Try / catch

try {
  await billingOperation();
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Sign in to ')) openSignIn();
  else throw e;
}

Prevention

When it happens

Trigger: Invoking any billing operation routed through requireSignedInUserId (such as claimProActivationPresentation) while signed out, after session expiry, before Clerk loads, or with ClerkProvider misconfigured.

Common situations: Billing UI rendered from cached state after logout; expired sessions on long-lived dashboards; dev builds without Clerk env vars.

Related errors


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