{"record":{"id":"4bef37709fdccb61","repo":"koala73/worldmonitor","slug":"http-response-status","errorCode":null,"errorMessage":"HTTP ${response.status}","messagePattern":"HTTP \\$\\{response\\.status\\}","errorType":"http","errorClass":null,"httpStatus":502,"severity":"warning","filePath":"api/fwdstart.js","lineNumber":39,"sourceCode":" * terminator cannot close the section early (#7206).\n */\nexport function cdata(s) {\n  const cleaned = String(s ?? '').replace(ILLEGAL_XML_CHARS, '');\n  // `]]>` → `]]]]><![CDATA[>` so the terminator is split across sections\n  // and the literal `]]>` survives when CDATA bodies are concatenated.\n  return `<![CDATA[${cleaned.split(']]>').join(']]]]><![CDATA[>')}]]>`;\n}\n\n/** Fetch the archive page and extract post items. Throws on upstream failure. */\nasync function scrapeArchiveItems() {\n  const response = await fetch('https://www.fwdstart.me/archive', {\n    headers: {\n      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n      'Accept': 'text/html,application/xhtml+xml',\n    },\n    signal: AbortSignal.timeout(15000),\n  });\n\n  if (!response.ok) {\n    throw new Error(`HTTP ${response.status}`);\n  }\n\n  const html = await response.text();\n  const items = [];\n  const seenUrls = new Set();\n\n  // Split by embla__slide to get each post block\n  const slideBlocks = html.split('embla__slide');\n\n  for (const block of slideBlocks) {\n    // Extract URL\n    const urlMatch = block.match(/href=\"(\\/p\\/[^\"]+)\"/);\n    if (!urlMatch) continue;\n\n    const url = `https://www.fwdstart.me${urlMatch[1]}`;\n    if (seenUrls.has(url)) continue;","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/fwdstart.js#L21-L57","documentation":"createApiKey() captures the Clerk userId up front, then calls waitForConvexAuthForUser(userId) (src/services/convex-client.ts:311), which returns false when the shared ConvexClient's auth barrier belongs to a different user, the Clerk user changed since capture, the barrier was superseded by a newer setAuth generation, or the token did not become ready within the 10s default timeout. The mutation is aborted so one account can never mint a key bound to another; a key was never generated server-side.","triggerScenarios":"Sign-out/sign-in as another user between capturing userId and the auth gate; sign-out mid-operation; a concurrent auth rebind (startConvexAuthRebind) replacing the barrier generation; token fetch slower than 10s.","commonSituations":"Users switching accounts in another tab; Clerk token refresh storms; flaky networks making the 10s barrier timeout look like an account change.","solutions":["Retry the creation after sign-in settles; the rebind usually completes within seconds","If it repeats, confirm the same user stays signed in across all tabs during the operation","Check console logs for repeated auth rebind/authGeneration churn","In UI, treat this message as 'please try again' rather than a dead-end error"],"exampleFix":"// before\nawait createApiKey(name); // 'Account changed while creating the API key. Try again.'\n\n// after: re-check identity right before the call and retry once on this specific error\nconst userId = getCurrentClerkUser()?.id;\nif (!userId) { openSignIn(); return; }\ntry {\n  await createApiKey(name);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Account changed') && getCurrentClerkUser()?.id === userId) {\n    await createApiKey(name); // one retry after rebind settles\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"const userId = getCurrentClerkUser()?.id;\nif (!userId) { openSignIn(); return; }\n// re-verify right before the call so the captured identity is still current\nif (getCurrentClerkUser()?.id !== userId) { abortStaleOperation(); return; }\nawait createApiKey(name);","typeGuard":"const isAccountChangedError = (e: unknown): e is Error =>\n  e instanceof Error && e.message.startsWith('Account changed');","tryCatchPattern":"try {\n  await createApiKey(name);\n} catch (e) {\n  if (isAccountChangedError(e) && getCurrentClerkUser()?.id === userId) {\n    await new Promise(r => setTimeout(r, 1000));\n    await createApiKey(name); // rebind usually settles within seconds\n  } else throw e;\n}","preventionTips":["Capture userId before any await and re-check identity immediately before the mutation","Avoid account switching while key operations are in flight","Treat 'Account changed ... Try again.' as retryable by design, never as data loss"],"tags":["clerk","convex","auth-race","account-switch","api-keys"],"backgroundTag":"session-user-mismatch","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"}