{"record":{"id":"b4bb18e0e42e9217","repo":"sipeed/picoclaw","slug":"failed-to-query-wecom-qr-result-w","errorCode":null,"errorMessage":"failed to query WeCom QR result: %w","messagePattern":"failed to query WeCom QR result: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/auth/wecom.go","lineNumber":334,"sourceCode":"\t\tcase <-timeoutCtx.Done():\n\t\t\tif errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {\n\t\t\t\treturn wecomQRBotInfo{}, fmt.Errorf(\"WeCom QR scan timed out after %s\", opts.PollTimeout)\n\t\t\t}\n\t\t\treturn wecomQRBotInfo{}, timeoutCtx.Err()\n\t\tcase <-time.After(opts.PollInterval):\n\t\t}\n\t}\n}\n\nfunc queryWeComQRCodeStatus(ctx context.Context, opts wecomQRFlowOptions, scode string) (wecomQRQueryResponse, error) {\n\tqueryURL, err := buildWeComQRQueryURL(opts.QueryURL, scode)\n\tif err != nil {\n\t\treturn wecomQRQueryResponse{}, err\n\t}\n\n\tvar resp wecomQRQueryResponse\n\tif err := doWeComJSONGet(ctx, opts.HTTPClient, queryURL, &resp); err != nil {\n\t\treturn wecomQRQueryResponse{}, fmt.Errorf(\"failed to query WeCom QR result: %w\", err)\n\t}\n\tif resp.ErrCode != 0 {\n\t\treturn wecomQRQueryResponse{}, fmt.Errorf(\n\t\t\t\"failed to query WeCom QR result: errcode=%d errmsg=%s\",\n\t\t\tresp.ErrCode,\n\t\t\tresp.ErrMsg,\n\t\t)\n\t}\n\n\treturn resp, nil\n}\n\nfunc buildWeComQRGenerateURL(baseURL, sourceID string, platformCode int) (string, error) {\n\tu, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid WeCom QR generate URL: %w\", err)\n\t}\n","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/auth/wecom.go#L316-L352","documentation":"A single poll iteration failed at the HTTP layer: doWeComJSONGet returned an error while GETting the query URL. This wraps transport failures, non-200 statuses ('unexpected status 502 Bad Gateway...'), and body decode failures ('decode JSON response...'). Unlike DeadlineExceeded, any other error here aborts the whole login immediately — one transient 502 kills the flow.","triggerScenarios":"A single 502/503 from a load balancer between polls; TLS reset mid-request; response body was HTML (proxy error page) so JSON decode failed; HTTP timeout on one poll exceeding wecomQRHTTPTimeout.","commonSituations":"Relay behind a flaky CDN; momentary network switch (Wi-Fi to VPN); captive portal intercepting mid-flow; relay rolling deploy causing one 502.","solutions":["Check whether the failure is persistent (curl the query endpoint) or was a one-off, then simply retry the login","Stabilize network/VPN before starting an interactive login","Update picoclaw — newer builds may tolerate transient poll errors by continuing the loop","If a proxy intercepts, bypass it for the WeCom relay domain"],"exampleFix":"// before\nstatus, err := queryWeComQRCodeStatus(timeoutCtx, opts, scode)\nif err != nil {\n\tif errors.Is(err, context.DeadlineExceeded) || errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {\n\t\treturn wecomQRBotInfo{}, fmt.Errorf(\"WeCom QR scan timed out after %s\", opts.PollTimeout)\n\t}\n\treturn wecomQRBotInfo{}, err\n}\n\n// after: tolerate isolated poll failures until the overall deadline\nstatus, err := queryWeComQRCodeStatus(timeoutCtx, opts, scode)\nif err != nil {\n\tif errors.Is(timeoutCtx.Err(), context.DeadlineExceeded) {\n\t\treturn wecomQRBotInfo{}, fmt.Errorf(\"WeCom QR scan timed out after %s\", opts.PollTimeout)\n\t}\n\tif !errors.Is(err, context.Canceled) {\n\t\tselect {\n\t\tcase <-timeoutCtx.Done():\n\t\t\treturn wecomQRBotInfo{}, fmt.Errorf(\"WeCom QR scan timed out after %s\", opts.PollTimeout)\n\t\tcase <-time.After(opts.PollInterval):\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn wecomQRBotInfo{}, err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isTransientPollErr(err error) bool {\n\tvar netErr net.Error\n\tif errors.As(err, &netErr) { return true }\n\treturn strings.Contains(err.Error(), \"unexpected status 50\") ||\n\t\tstrings.Contains(err.Error(), \"unexpected status 429\")\n}","tryCatchPattern":"status, err := queryWeComQRCodeStatus(timeoutCtx, opts, scode)\nif err != nil && isTransientPollErr(err) && timeoutCtx.Err() == nil {\n\tselect {\n\tcase <-timeoutCtx.Done():\n\t\treturn wecomQRBotInfo{}, fmt.Errorf(\"WeCom QR scan timed out after %s\", opts.PollTimeout)\n\tcase <-time.After(opts.PollInterval):\n\t\tcontinue\n\t}\n}","preventionTips":["Treat single poll failures as retryable until the overall deadline","Only abort immediately on context.Canceled or persistent errors"],"tags":["go","wecom","network","polling"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}