{"record":{"id":"13df25b104fdace6","repo":"CherryHQ/cherry-studio","slug":"qr-login-failed-after-max-qr-retries-expired-qr","errorCode":null,"errorMessage":"QR login failed after ${MAX_QR_RETRIES} expired QR codes. Use config tool to reconnect.","messagePattern":"QR login failed after (.+?) expired QR codes\\. Use config tool to reconnect\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/adapters/wechat/WeChatProtocol.ts","lineNumber":653,"sourceCode":"        const credentials: Credentials = {\n          token: status.bot_token,\n          baseUrl: status.baseurl ?? options.baseUrl,\n          accountId: status.ilink_bot_id,\n          userId: status.ilink_user_id\n        }\n        await saveCredentials(credentials, options.tokenPath)\n        return credentials\n      }\n\n      if (status.status === 'expired') break\n\n      await delay(QR_POLL_INTERVAL_MS)\n    }\n\n    qrRetries++\n  }\n\n  throw new Error(`QR login failed after ${MAX_QR_RETRIES} expired QR codes. Use config tool to reconnect.`)\n}\n\n// --------------- WeixinBot ---------------\n\ntype MessageHandler = (msg: IncomingMessage) => void | Promise<void>\n\nexport interface WeixinBotOptions {\n  baseUrl?: string\n  tokenPath?: string\n  onError?: (error: unknown) => void\n  onQrUrl?: (url: string) => void\n}\n\n/** Normalize a base URL to origin form (no trailing slash). */\nfunction normalizeBaseUrl(baseUrl: string): string {\n  return baseUrl.replace(/\\/+$/, '')\n}\n","sourceCodeStart":635,"sourceCodeEnd":671,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/adapters/wechat/WeChatProtocol.ts#L635-L671","documentation":"Thrown by loginFlow() after MAX_QR_RETRIES (3) QR codes have expired in succession without a confirmed scan. Each QR code is generated via fetchQrCode, polled until status:'expired', and the loop increments qrRetries. After 3 expired QRs (so 3 full generate→poll→expired cycles, each lasting however long WeChat keeps a QR valid), the loop exits and throws this. The message directs the user to the config tool to reconnect. This is a deliberate user-facing failure: the login window elapsed without action three times.","triggerScenarios":"Three consecutive QR codes reach 'expired' status. For each: fetchQrCode returns a QR, the inner loop polls pollQrStatus every QR_POLL_INTERVAL_MS (2s), status transitions wait→scaned→confirmed OR wait→expired. If expired, the inner loop breaks (line 645), qrRetries increments, and if it reaches MAX_QR_RETRIES=3, the while loop exits and this throws. Note: a QR that is generated but never scanned within its validity window expires.","commonSituations":"The user walked away from the screen with the QR displayed; the QR image was not rendered correctly in the UI (the onQrUrl callback delivered a data URL that did not display) so the user never saw it; the user's WeChat app could not scan (camera/camera-permission issue); the user scanned with the wrong WeChat account; network issues prevented the scan status from reaching the server.","solutions":["Increase MAX_QR_RETRIES if 3 is too few for the use case — but better to fix the underlying UX (auto-regenerate the QR without a hard cap).","Verify the QR is actually displayed: the onQrUrl callback (WeChatAdapter.ts:41) emits to renderer — check the QR rendering path end-to-end.","Restart the login via the config tool as the message suggests (WeixinBot.login({force:true})).","Check that the device scanning the QR has the WeChat app and camera permission, and is scanning within the QR validity window (~1-2 minutes)."],"exampleFix":"// before — hard cap of 3, then a fatal throw\nwhile (qrRetries < MAX_QR_RETRIES) {\n  // ...\n  qrRetries++\n}\nthrow new Error(`QR login failed after ${MAX_QR_RETRIES} expired QR codes. Use config tool to reconnect.`)\n\n// after — auto-regenerate expired QRs indefinitely while the login view is open\nwhile (!options.signal?.aborted) {\n  // generate QR, poll until confirmed or expired\n  // ...\n  if (status.status === 'expired') {\n    options.onQrUrl?.(null) // clear old QR in UI\n    continue // regenerate without incrementing a hard cap\n  }\n}","handlingStrategy":"retry","validationCode":"// The hard cap (MAX_QR_RETRIES=3) is the throw's cause. To avoid hitting it,\n// ensure the QR is actually displayed and scannable. Verify the onQrUrl callback\n// delivers a renderable data URL before starting login:\nasync function startWeChatLogin(bot: WeixinBot, signal: AbortSignal, onQr: (url: string) => void) {\n  let qrDisplayed = false\n  const wrappedOnQr = (url: string) => {\n    qrDisplayed = true\n    onQr(url)\n  }\n  const loginPromise = bot.login({ signal, onQrUrl: wrappedOnQr })\n  // If the QR never renders, abort early rather than letting 3 QRs expire silently\n  return loginPromise\n}","typeGuard":"// The throw is deterministic after MAX_QR_RETRIES expirations — no runtime type guard,\n// but you can detect the message to branch recovery:\nfunction isQrRetriesExhausted(e: unknown): boolean {\n  return e instanceof Error && /QR login failed after \\d+ expired QR codes/.test(e.message)\n}","tryCatchPattern":"// Auto-restart the QR flow one more time, or surface a clear user action\ntry {\n  await bot.login({ signal })\n} catch (e) {\n  if (isQrRetriesExhausted(e)) {\n    // Option A: restart the flow with a fresh WeixinBot\n    logger.warn('WeChat QR retries exhausted, restarting login once')\n    await bot.login({ force: true, signal })\n    // Option B: surface to user\n    // showUserError('WeChat QR expired 3 times — open Channel settings to scan a new QR')\n  } else {\n    throw e\n  }\n}","preventionTips":["Verify the QR data URL actually renders in the UI before relying on user scan — a broken <img> will silently exhaust retries.","Increase MAX_QR_RETRIES or remove the hard cap and auto-regenerate expired QRs while the login view is open.","Check the user's WeChat app camera permission if scans never register.","Ensure the device scanning is the correct WeChat account tied to the expected workspace."],"tags":["wechat","login","qr","user-action-required","retry-exhausted"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}