sipeed/picoclaw · warning

login timeout

Error message

login timeout

What it means

The Weixin QR login poller hit its deadline: within opts.Timeout (default 5 minutes) the QR code never reached the "confirmed" state. The loop polls GetQRCodeStatus every 2 seconds; the timeout context fires while status is still "wait"/"scaned", or while poll requests keep erroring (poll errors are swallowed with continue, so a persistently failing poll also ends here).

Source

Thrown at pkg/channels/weixin/auth.go:78

		HalfBlocks: true,
	}
	qrterminal.GenerateWithConfig(qrResp.QrcodeImgContent, qrconfig)

	fmt.Printf("\nQR Code Link: %s\n\n", qrResp.QrcodeImgContent)
	fmt.Println("Waiting for scan...")

	timeoutCtx, cancel := context.WithTimeout(ctx, opts.Timeout)
	defer cancel()

	pollTicker := time.NewTicker(2 * time.Second)
	defer pollTicker.Stop()

	scannedPrinted := false

	for {
		select {
		case <-timeoutCtx.Done():
			return "", "", "", "", fmt.Errorf("login timeout")
		case <-pollTicker.C:
			statusResp, err := pollAPI.GetQRCodeStatus(timeoutCtx, qrResp.Qrcode)
			if err != nil {
				// Long poll timeout or temporary error
				continue
			}

			switch statusResp.Status {
			case "wait":
				// still waiting
			case "scaned":
				if !scannedPrinted {
					fmt.Println("👀 QR Code scanned! Please confirm login on your WeChat app...")
					scannedPrinted = true
				}
			case "confirmed":
				if statusResp.BotToken == "" || statusResp.IlinkBotID == "" {
					return "", "", "", "", fmt.Errorf("login confirmed but missing bot_token or ilink_bot_id")

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Simply re-run the login command and scan the new QR promptly
  2. Raise opts.Timeout (e.g. 10*time.Minute) if the operator needs more time
  3. Make sure the host running login has stable network access to the polling endpoint so polls are not silently skipped
  4. Watch the console output: "QR Code scanned" means confirm on the phone is the missing step

Example fix

// before
opts := weixin.LoginOpts{Timeout: 5 * time.Minute}

// after
opts := weixin.LoginOpts{Timeout: 10 * time.Minute}
Defensive patterns

Strategy: retry

Validate before calling

// assert a sane timeout and an operator before starting
if opts.Timeout < 2*time.Minute {
    opts.Timeout = 5 * time.Minute
}
if !operatorReadyToScan() {
    return errors.New("no operator present; aborting login")
}

Type guard

func isWeixinLoginTimeout(err error) bool {
    return err != nil && strings.Contains(err.Error(), "login timeout")
}

Try / catch

if _, _, _, _, err := weixin.Login(ctx, opts); err != nil {
    if isWeixinLoginTimeout(err) {
        // benign: re-run with a fresh QR; consider a longer Timeout
        opts.Timeout *= 2
        _, _, _, _, err = weixin.Login(ctx, opts)
    }
}

Prevention

When it happens

Trigger: Starting interactive login and not scanning+confirming the QR in time; network to the polling endpoint failing for the whole window so every status poll errors and loops; the user scanned but never tapped confirm on the phone; the QR expired (status "expired" would normally return error 672 first, but a missed final poll can time out instead).

Common situations: Unattended login attempt (nobody scans); user distracted during the 5-minute window; flaky network dropping every poll; timeout lowered for tests; slow phone confirmation on a large window where the last poll raced the timer.

Understand the failure class

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/63e5cdf2d1843026. Report an issue: GitHub.