zeroclaw-labs/zeroclaw · error · anyhow::Error
WeChat QR code expired {$max} times, giving up.
Error message
WeChat QR code expired {$max} times, giving up. What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/wechat.rs:1645
/// Perform QR-code login flow. Returns (bot_token, account_id, user_id).
async fn qr_login(&self) -> anyhow::Result<(String, String, Option<String>)> {
let mut qr_refresh_count = 0u32;
loop {
qr_refresh_count += 1;
if qr_refresh_count > MAX_QR_REFRESH {
let max = MAX_QR_REFRESH.to_string();
let reason = wechat_cli_string_with_args(
"cli-wechat-qr-expired-giving-up",
&[("max", &max)],
);
crate::login_events::LoginEvent::Failed { reason: &reason }.emit(
self.name(),
&self.alias,
"WeChat QR login gave up after repeated expiry",
);
anyhow::bail!("{reason}");
}
// Fetch QR code
let qr_url = format!("{}?bot_type=3", self.api_url("get_bot_qrcode"));
let resp = self
.client
.get(&qr_url)
.timeout(API_TIMEOUT)
.send()
.await
.with_context(|| wechat_cli_string("cli-wechat-qr-fetch-failed"))?;
if !resp.status().is_success() {
let status = resp.status().to_string();
let body = resp.text().await.unwrap_or_default();
anyhow::bail!(
"{}",
wechat_cli_string_with_args(View on GitHub (pinned to 88bb9c8533)
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
Example fix
// before
channel.login().await?; // dies after 3 QR expiries
// after
for attempt in 0..5 {
match channel.login().await {
Ok(()) => break,
Err(e) if e.to_string().contains("QR code expired") && attempt < 4 => continue,
Err(e) => return Err(e),
}
} Defensive patterns
Strategy: retry
Try / catch
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.
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- QR payload is empty
- WeChat QR code fetch failed ({$status}): {$body}
- getUploadUrl failed ({status}): {body}
- createSession failed ({status}): {body}
- Cannot persist empty {channel_type} identity
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/9fb4aef8e6960823.
Report an issue: GitHub.