{"record":{"id":"3d74c4b21cd7a8ae","repo":"koala73/worldmonitor","slug":"invalid-user-id","errorCode":"INVALID_USER_ID","errorMessage":"INVALID_USER_ID","messagePattern":"INVALID_USER_ID","errorType":"exception","errorClass":"ConvexError","httpStatus":400,"severity":"error","filePath":"convex/mcpProTokens.ts","lineNumber":48,"sourceCode":" *\n * Called from the edge at `/oauth/authorize-pro` after the cross-subdomain\n * Clerk grant has been validated. The caller passes the verified Clerk\n * `userId`. Verifies active Pro MCP entitlement defensively; the edge checks\n * too, but this mutation is the authoritative row-insertion gate.\n *\n * Per-user 5-row cap with silent oldest rotation: if the user already has\n * 5 active rows we revoke the oldest (by createdAt) before inserting the\n * new one — never delete (preserves audit trail).\n */\nexport const issueProMcpToken = internalMutation({\n  args: {\n    userId: v.string(),\n    clientId: v.optional(v.string()),\n    name: v.optional(v.string()),\n  },\n  handler: async (ctx, args) => {\n    if (!args.userId) {\n      throw new ConvexError(\"INVALID_USER_ID\");\n    }\n\n    const entitlement = await ctx.db\n      .query(\"entitlements\")\n      .withIndex(\"by_userId\", (q) => q.eq(\"userId\", args.userId))\n      .first();\n    const mergedFeatures = entitlement\n      ? mergeEntitlementFeatures(entitlement.planKey, entitlement.features)\n      : null;\n    const isPro = Boolean(\n      entitlement\n      && mergedFeatures\n      && entitlement.validUntil >= Date.now()\n      && mergedFeatures.tier >= 1\n      && mergedFeatures.mcpAccess === true,\n    );\n    // #6716 — a CONFIRMED free account may also hold a token.\n    //","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/convex/mcpProTokens.ts#L30-L66","documentation":"Validation in the issueProMcpToken internal mutation: the supplied userId is not a valid user identifier (empty or malformed). The edge caller at /oauth/authorize-pro passes the verified Clerk userId after the cross-subdomain grant check; an invalid one means the grant validation upstream produced garbage.","triggerScenarios":"Thrown at convex/mcpProTokens.ts:48 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Verify the Clerk grant validation at /oauth/authorize-pro extracts and passes the real userId","Reject the authorize request upstream if the verified subject is empty"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}