koala73/worldmonitor · warning
HTTP
Error message
HTTP ${response.status} What it means
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.
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
Example fix
// before
await createApiKey(name); // 'Account changed while creating the API key. Try again.'
// after: re-check identity right before the call and retry once on this specific error
const userId = getCurrentClerkUser()?.id;
if (!userId) { openSignIn(); return; }
try {
await createApiKey(name);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Account changed') && getCurrentClerkUser()?.id === userId) {
await createApiKey(name); // one retry after rebind settles
} else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
const userId = getCurrentClerkUser()?.id;
if (!userId) { openSignIn(); return; }
// re-verify right before the call so the captured identity is still current
if (getCurrentClerkUser()?.id !== userId) { abortStaleOperation(); return; }
await createApiKey(name); Type guard
const isAccountChangedError = (e: unknown): e is Error =>
e instanceof Error && e.message.startsWith('Account changed'); Try / catch
try {
await createApiKey(name);
} catch (e) {
if (isAccountChangedError(e) && getCurrentClerkUser()?.id === userId) {
await new Promise(r => setTimeout(r, 1000));
await createApiKey(name); // rebind usually settles within seconds
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Users switching accounts in another tab; Clerk token refresh storms; flaky networks making the 10s barrier timeout look like an account change.
Related errors
- MCP_INTERNAL_HMAC_SECRET not configured
- REDIS_DOWN
- Account changed while creating the embed key. Try again.
- Account changed while revoking the embed key. Try again.
- ALREADY_REVOKED
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21).
Data as JSON: /api/errors/4bef37709fdccb61.
Report an issue: GitHub.
Appendix: source
Thrown at api/fwdstart.js:39
* terminator cannot close the section early (#7206).
*/
export function cdata(s) {
const cleaned = String(s ?? '').replace(ILLEGAL_XML_CHARS, '');
// `]]>` → `]]]]><![CDATA[>` so the terminator is split across sections
// and the literal `]]>` survives when CDATA bodies are concatenated.
return `<![CDATA[${cleaned.split(']]>').join(']]]]><![CDATA[>')}]]>`;
}
/** Fetch the archive page and extract post items. Throws on upstream failure. */
async function scrapeArchiveItems() {
const response = await fetch('https://www.fwdstart.me/archive', {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'text/html,application/xhtml+xml',
},
signal: AbortSignal.timeout(15000),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const html = await response.text();
const items = [];
const seenUrls = new Set();
// Split by embla__slide to get each post block
const slideBlocks = html.split('embla__slide');
for (const block of slideBlocks) {
// Extract URL
const urlMatch = block.match(/href="(\/p\/[^"]+)"/);
if (!urlMatch) continue;
const url = `https://www.fwdstart.me${urlMatch[1]}`;
if (seenUrls.has(url)) continue;View on GitHub (pinned to 7d06c8633d)