{"record":{"id":"04ebcc3953e70c4d","repo":"sipeed/picoclaw","slug":"login-confirmed-but-missing-bot-token-or-ilink-bot","errorCode":null,"errorMessage":"login confirmed but missing bot_token or ilink_bot_id","messagePattern":"login confirmed but missing bot_token or ilink_bot_id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/weixin/auth.go","lineNumber":96,"sourceCode":"\t\t\treturn \"\", \"\", \"\", \"\", fmt.Errorf(\"login timeout\")\n\t\tcase <-pollTicker.C:\n\t\t\tstatusResp, err := pollAPI.GetQRCodeStatus(timeoutCtx, qrResp.Qrcode)\n\t\t\tif err != nil {\n\t\t\t\t// Long poll timeout or temporary error\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch statusResp.Status {\n\t\t\tcase \"wait\":\n\t\t\t\t// still waiting\n\t\t\tcase \"scaned\":\n\t\t\t\tif !scannedPrinted {\n\t\t\t\t\tfmt.Println(\"👀 QR Code scanned! Please confirm login on your WeChat app...\")\n\t\t\t\t\tscannedPrinted = true\n\t\t\t\t}\n\t\t\tcase \"confirmed\":\n\t\t\t\tif statusResp.BotToken == \"\" || statusResp.IlinkBotID == \"\" {\n\t\t\t\t\treturn \"\", \"\", \"\", \"\", fmt.Errorf(\"login confirmed but missing bot_token or ilink_bot_id\")\n\t\t\t\t}\n\t\t\t\tlogger.InfoCF(\"weixin\", \"Login successful\", map[string]any{\n\t\t\t\t\t\"account_id\": statusResp.IlinkBotID,\n\t\t\t\t})\n\n\t\t\t\treturn statusResp.BotToken, statusResp.IlinkUserID, statusResp.IlinkBotID, statusResp.Baseurl, nil\n\t\t\tcase \"scaned_but_redirect\":\n\t\t\t\tif statusResp.RedirectHost == \"\" {\n\t\t\t\t\tlogger.WarnC(\n\t\t\t\t\t\t\"weixin\",\n\t\t\t\t\t\t\"scaned_but_redirect received without redirect_host; continuing on current host\",\n\t\t\t\t\t)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnextBaseURL := \"https://\" + statusResp.RedirectHost + \"/\"\n\t\t\t\tnextAPI, nextErr := NewApiClient(nextBaseURL, \"\", opts.Proxy)\n\t\t\t\tif nextErr != nil {\n\t\t\t\t\tlogger.WarnCF(\"weixin\", \"Failed to switch QR polling host\", map[string]any{","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/weixin/auth.go#L78-L114","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"null","typeGuard":"func isWeixinConfirmedMissingToken(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"confirmed but missing bot_token\")\n}","tryCatchPattern":"if _, _, _, _, err := weixin.Login(ctx, opts); err != nil {\n    if isWeixinConfirmedMissingToken(err) {\n        // server race: full retry usually succeeds with complete tokens\n        _, _, _, _, err = weixin.Login(ctx, opts)\n    }\n}","preventionTips":["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"],"tags":["weixin","auth","login","protocol","missing-token"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}