{"record":{"id":"9fb4aef8e6960823","repo":"zeroclaw-labs/zeroclaw","slug":"wechat-qr-code-expired-max-times-giving-up","errorCode":null,"errorMessage":"WeChat QR code expired {$max} times, giving up.","messagePattern":"WeChat QR code expired (.+?) times, giving up\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wechat.rs","lineNumber":1645,"sourceCode":"\n    /// Perform QR-code login flow. Returns (bot_token, account_id, user_id).\n    async fn qr_login(&self) -> anyhow::Result<(String, String, Option<String>)> {\n        let mut qr_refresh_count = 0u32;\n\n        loop {\n            qr_refresh_count += 1;\n            if qr_refresh_count > MAX_QR_REFRESH {\n                let max = MAX_QR_REFRESH.to_string();\n                let reason = wechat_cli_string_with_args(\n                    \"cli-wechat-qr-expired-giving-up\",\n                    &[(\"max\", &max)],\n                );\n                crate::login_events::LoginEvent::Failed { reason: &reason }.emit(\n                    self.name(),\n                    &self.alias,\n                    \"WeChat QR login gave up after repeated expiry\",\n                );\n                anyhow::bail!(\"{reason}\");\n            }\n\n            // Fetch QR code\n            let qr_url = format!(\"{}?bot_type=3\", self.api_url(\"get_bot_qrcode\"));\n            let resp = self\n                .client\n                .get(&qr_url)\n                .timeout(API_TIMEOUT)\n                .send()\n                .await\n                .with_context(|| wechat_cli_string(\"cli-wechat-qr-fetch-failed\"))?;\n\n            if !resp.status().is_success() {\n                let status = resp.status().to_string();\n                let body = resp.text().await.unwrap_or_default();\n                anyhow::bail!(\n                    \"{}\",\n                    wechat_cli_string_with_args(","sourceCodeStart":1627,"sourceCodeEnd":1663,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wechat.rs#L1627-L1663","documentation":"During QR-code login the WeChat channel polls for a scan; when the QR code expires it refreshes it, tracking qr_refresh_count. Once the count exceeds MAX_QR_REFRESH = 3 (wechat.rs:39) it emits LoginEvent::Failed ('WeChat QR login gave up after repeated expiry') and bails with the localized 'QR code expired {max} times, giving up' reason.","triggerScenarios":"Starting WeChat channel login and nobody scanning the QR code before expiry, four times in a row (initial code plus three refreshes). Each expiry increments qr_refresh_count; exceeding 3 aborts the login attempt.","commonSituations":"Unattended bot startup where no one is watching the QR; the QR is printed to a log or console nobody sees; the WeChat mobile app never accepts the scan (device offline, account restricted) so every code lapses.","solutions":["Restart the login flow to get a fresh QR session — the refresh counter resets per login attempt — and scan promptly","Make sure the QR is actually rendered where a human can scan it (terminal, dashboard via LoginEvent, forwarded image channel)","If scans never register, verify the WeChat account is not restricted and the iLink api_url/token is valid","Automate recovery: listen for LoginEvent::Failed and re-invoke login with a bounded retry cap"],"exampleFix":"// before\nchannel.login().await?; // dies after 3 QR expiries\n// after\nfor attempt in 0..5 {\n    match channel.login().await {\n        Ok(()) => break,\n        Err(e) if e.to_string().contains(\"QR code expired\") && attempt < 4 => continue,\n        Err(e) => return Err(e),\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"Catch the login error, test for 'QR code expired' in the message, and re-invoke login() for a fresh QR session; cap the outer loop (e.g. 5 attempts) so a genuinely unattended bot does not spin forever.","preventionTips":["Render the QR immediately when the login prompt event fires — in a terminal, dashboard, or forwarded image","Scan within the QR validity window (roughly one to two minutes)","Subscribe to LoginEvent::Failed to automate re-login with a bounded retry"],"tags":["wechat","login","qr-code","authentication","retry-limit"],"backgroundTag":"qr-login-expired","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}