{"record":{"id":"d3a74afb70c64ba2","repo":"koala73/worldmonitor","slug":"buildauthheaders-free-tier-context-has-no-credent","errorCode":null,"errorMessage":"buildAuthHeaders: free-tier context has no credentials — a free-tier tool must not call a credentialed downstream","messagePattern":"buildAuthHeaders: free-tier context has no credentials — a free-tier tool must not call a credentialed downstream","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"api/mcp/auth.ts","lineNumber":138,"sourceCode":"  mcpProMinRatelimits.set(perMinute, limiter);\n  return limiter;\n}\n\nfunction getMcpAnonRatelimit(): Ratelimit | null {\n  if (mcpAnonRatelimit) return mcpAnonRatelimit;\n  const url = process.env.UPSTASH_REDIS_REST_URL;\n  const token = process.env.UPSTASH_REDIS_REST_TOKEN;\n  if (!url || !token) return null;\n  mcpAnonRatelimit = new Ratelimit({\n    redis: new Redis({ url, token, retry: false }),\n    limiter: Ratelimit.slidingWindow(60, '60 s'),\n    prefix: 'rl:mcp:anon',\n    analytics: false,\n  });\n  return mcpAnonRatelimit;\n}\n\n/**\n * Build the Authorization header set for a downstream `_execute` fetch.\n *\n *   - env_key → `X-WorldMonitor-Key: <apiKey>` (legacy operator keys).\n *   - pro / user_key → `X-WM-MCP-Internal: <ts>.<sig>` + `X-WM-MCP-User-Id`.\n *               Signature binds method+pathname+queryHash+bodyHash+userId.\n *               Identity-resolved dashboard keys use this path so the\n *               gateway does not increment the shared daily account meter\n *               a second time after MCP already reserved the tool weight.\n *\n * `body` MUST be the EXACT bytes the caller passes to `fetch()` so the\n * signed payload matches the wire bytes. For JSON, pre-stringify on the\n * caller side and pass the same string here.\n */\nexport async function buildAuthHeaders(\n  context: McpAuthContext,\n  method: string,\n  url: string,\n  body: BodyInit | null | undefined,","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/mcp/auth.ts#L120-L156","documentation":"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.","triggerScenarios":"Invoking any billing operation routed through requireSignedInUserId (such as claimProActivationPresentation) while signed out, after session expiry, before Clerk loads, or with ClerkProvider misconfigured.","commonSituations":"Billing UI rendered from cached state after logout; expired sessions on long-lived dashboards; dev builds without Clerk env vars.","solutions":["Sign in and retry the billing action","Ensure Clerk is fully loaded before invoking billing flows","Gate billing buttons on auth state so the call never fires signed-out","Check ClerkProvider/publishable key configuration if the user should be signed in"],"exampleFix":"// before\nconst status = await claimProActivationPresentation(activationKey, claimNonce);\n// 'Sign in to claim Pro activation.'\n\n// after\nif (!getCurrentClerkUser()?.id) {\n  openSignIn();\n  return;\n}\nconst status = await claimProActivationPresentation(activationKey, claimNonce);","handlingStrategy":"validation","validationCode":"if (!getCurrentClerkUser()?.id) { openSignIn(); return; }\nconst outcome = await claimProActivationPresentation(activationKey, claimNonce);","typeGuard":"const hasActiveClerkUser = (u: { id: string } | null | undefined): u is { id: string } =>\n  typeof u?.id === 'string' && u.id.length > 0;","tryCatchPattern":"try {\n  await billingOperation();\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Sign in to ')) openSignIn();\n  else throw e;\n}","preventionTips":["Route all billing UI through live auth state; the action name in the message identifies the flow","Do not call billing functions during the Clerk loading window","Prefix-matching 'Sign in to ' covers every current and future billing action string"],"tags":["clerk","auth","billing","precondition"],"backgroundTag":"user-not-authenticated","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}