sipeed/picoclaw · error

login confirmed but missing bot_token or ilink_bot_id

Error message

login confirmed but missing bot_token or ilink_bot_id

What it means

The QR status poll returned "confirmed" but the response lacked bot_token or ilink_bot_id, so login cannot complete even though the user approved it. This is a server-contract violation or partial state: the flow returns empty credentials plus this error rather than proceeding with a half-initialized session. The other confirmed-session fields (user id, base url) are discarded.

Source

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

			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")
				}
				logger.InfoCF("weixin", "Login successful", map[string]any{
					"account_id": statusResp.IlinkBotID,
				})

				return statusResp.BotToken, statusResp.IlinkUserID, statusResp.IlinkBotID, statusResp.Baseurl, nil
			case "scaned_but_redirect":
				if statusResp.RedirectHost == "" {
					logger.WarnC(
						"weixin",
						"scaned_but_redirect received without redirect_host; continuing on current host",
					)
					continue
				}
				nextBaseURL := "https://" + statusResp.RedirectHost + "/"
				nextAPI, nextErr := NewApiClient(nextBaseURL, "", opts.Proxy)
				if nextErr != nil {
					logger.WarnCF("weixin", "Failed to switch QR polling host", map[string]any{

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Retry the whole login flow — the usual case is a transient server race and the next attempt confirms cleanly
  2. Enable debug logging of the raw status response to see whether token fields are present under different names
  3. If field names changed, update QRCodeStatus struct tags to the current iLink schema
  4. Confirm only after "scaned" was observed (the normal sequence); confirming extremely fast can hit the race more often
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

func isWeixinConfirmedMissingToken(err error) bool {
    return err != nil && strings.Contains(err.Error(), "confirmed but missing bot_token")
}

Try / catch

if _, _, _, _, err := weixin.Login(ctx, opts); err != nil {
    if isWeixinConfirmedMissingToken(err) {
        // server race: full retry usually succeeds with complete tokens
        _, _, _, _, err = weixin.Login(ctx, opts)
    }
}

Prevention

When it happens

Trigger: A GetQRCodeStatus response with status="confirmed" and an empty BotToken or IlinkBotID — e.g. server-side race where confirmation is recorded before tokens are minted, an API schema change renaming the JSON fields, or a response routed through a redirect host that returns a different shape.

Common situations: Rare edge right after scanning: user confirms during heavy server load; a Weixin iLink API update changes field names so the Go struct no longer binds them; polling switched to a redirect host whose variant of the endpoint omits tokens.

Related errors


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