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
- Retry the whole login flow — the usual case is a transient server race and the next attempt confirms cleanly
- Enable debug logging of the raw status response to see whether token fields are present under different names
- If field names changed, update QRCodeStatus struct tags to the current iLink schema
- 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
- Retry the whole login on this rare server-side race
- Log the raw confirmed-status payload when it recurs to catch schema changes
- Keep struct field tags in QRCodeStatus aligned with the current iLink API
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
- failed to create api client: %w
- failed to get qrcode: %w
- login timeout
- qrcode expired, please try again
- failed to load config: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/04ebcc3953e70c4d.
Report an issue: GitHub.