{"record":{"id":"0d004ebd421b5293","repo":"koala73/worldmonitor","slug":"unauthenticated-0d004e","errorCode":"UNAUTHENTICATED","errorMessage":"UNAUTHENTICATED","messagePattern":"UNAUTHENTICATED","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/notificationChannels.ts","lineNumber":479,"sourceCode":"    if (!identity) return [];\n    return await ctx.db\n      .query(\"notificationChannels\")\n      .withIndex(\"by_user\", (q) => q.eq(\"userId\", identity.subject))\n      .collect();\n  },\n});\n\nexport const setChannel = mutation({\n  args: {\n    channelType: channelTypeValidator,\n    chatId: v.optional(v.string()),\n    webhookEnvelope: v.optional(v.string()),\n    email: v.optional(v.string()),\n    webhookLabel: v.optional(v.string()),\n  },\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) throw new ConvexError(\"UNAUTHENTICATED\");\n    const userId = identity.subject;\n    await assertProEntitlement(ctx, userId);\n\n    const existing = await ctx.db\n      .query(\"notificationChannels\")\n      .withIndex(\"by_user_channel\", (q) =>\n        q.eq(\"userId\", userId).eq(\"channelType\", args.channelType),\n      )\n      .unique();\n\n    const now = Date.now();\n\n    if (args.channelType === \"telegram\") {\n      if (!args.chatId) throw new ConvexError(\"chatId required for telegram channel\");\n      const doc = { userId, channelType: \"telegram\" as const, chatId: args.chatId, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/notificationChannels.ts#L461-L497","documentation":"Thrown by the `setChannel` mutation when `ctx.auth.getUserIdentity()` is falsy. This is the auth gate before the Pro-entitlement check and channel-specific validation. Plain-string ConvexError; `err.data === \"UNAUTHENTICATED\"`. Note this module uses string throws (branch on `err.data === \"UNAUTHENTICATED\"`), unlike followedCountries which uses `{kind}` objects.","triggerScenarios":"Calling `api.notificationChannels.setChannel` without an authenticated Clerk session; the session expired while the notification settings panel was open; calling from a context with no forwarded auth token.","commonSituations":"Session expiry on an open settings tab; a logged-out user reaches the panel via deep link; the auth token isn't attached to the Convex client after a provider config change.","solutions":["Gate the channel settings UI on `useAuth().isSignedIn`.","On `err.data === \"UNAUTHENTICATED\"`, redirect to sign-in and resume the channel setup after re-auth.","Re-fetch channel state after re-auth before retrying (another session may have changed it)."],"exampleFix":"// before\nconst save = () => convex.mutation(api.notificationChannels.setChannel, { channelType, chatId });\n\n// after\nconst { isSignedIn } = useAuth();\nconst save = async () => {\n  if (!isSignedIn) { navigate(\"/sign-in\"); return; }\n  try {\n    await convex.mutation(api.notificationChannels.setChannel, { channelType, chatId });\n  } catch (err) {\n    if (err.data === \"UNAUTHENTICATED\") navigate(\"/sign-in\");\n    else throw err;\n  }\n};","handlingStrategy":"try-catch","validationCode":"const { isSignedIn } = useAuth();\nif (!isSignedIn) { navigate(\"/sign-in\"); return; }","typeGuard":"// Auth gate via Clerk hook; no local type guard.","tryCatchPattern":"try {\n  await convex.mutation(api.notificationChannels.setChannel, { ... });\n} catch (err) {\n  if (err.data === \"UNAUTHENTICATED\") navigate(\"/sign-in\");\n  else throw err;\n}","preventionTips":["Gate the channel settings UI on `isSignedIn`.","This module uses plain-string ConvexError — branch on err.data === \"UNAUTHENTICATED\".","Re-fetch channels after re-auth before retrying.","Redirect to sign-in with a return path."],"tags":["auth","convex","clerk","notifications"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}