{"record":{"id":"c7a7f490f3ce4c57","repo":"chenhg5/cc-connect","slug":"poll-request-w","errorCode":null,"errorMessage":"poll request: %w","messagePattern":"poll request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":917,"sourceCode":"\nfunc (p *Platform) poll(ctx context.Context, marker *int64) (*int64, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, p.apiBase+\"/updates\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp.setAuth(req)\n\tq := req.URL.Query()\n\tq.Set(\"timeout\", strconv.Itoa(pollTimeout))\n\tq.Set(\"limit\", \"20\")\n\tq.Set(\"types\", \"message_created,message_callback\")\n\tif marker != nil {\n\t\tq.Set(\"marker\", strconv.FormatInt(*marker, 10))\n\t}\n\treq.URL.RawQuery = q.Encode()\n\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"poll request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn nil, fmt.Errorf(\"poll: HTTP %d: %s\", resp.StatusCode, body)\n\t}\n\n\tvar result maxUpdatesResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"poll decode: %w\", err)\n\t}\n\n\tfor i := range result.Updates {\n\t\tp.handleUpdate(ctx, &result.Updates[i])\n\t}\n\n\treturn result.Marker, nil","sourceCodeStart":899,"sourceCodeEnd":935,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L899-L935","documentation":"The MAX long-poll request to /updates fails at the transport layer: p.client.Do returned an error (connection refused, DNS failure, TLS problem, timeout, or context cancellation). The error is wrapped with the \"poll request: \" prefix. This is the entry point of the update polling loop, so repeated occurrences mean the bot cannot receive any messages.","triggerScenarios":"The poller calling getUpdates when the network is down, the MAX Bot API host is unreachable, p.client's Timeout fires on a long poll, the enclosing context is canceled during shutdown, or TLS interception breaks the handshake.","commonSituations":"Bot host offline or without internet; DNS misconfiguration; corporate proxies blocking botapi.max.ru; long-poll timeouts if p.client.Timeout is shorter than the server's long-poll hold; shutdown canceling the poll context (expected, usually benign).","solutions":["Distinguish shutdown from failure: if errors.Is(err, context.Canceled), exit the poll loop quietly instead of alarming/retrying.","Test reachability with curl to the /updates endpoint; fix DNS/proxy/firewall if unreachable.","Ensure p.client.Timeout exceeds the server's long-poll hold time (or use a dedicated poll client), otherwise every poll times out.","Add exponential backoff between failed polls to avoid hammering a down API and to ride out transient outages.","Verify the system clock and TLS roots are current — expired clocks cause x509 handshake failures that surface here."],"exampleFix":"// before\nresp, err := p.client.Do(req)\nif err != nil {\n\treturn nil, fmt.Errorf(\"poll request: %w\", err)\n}\n// after (caller treats cancellation as benign)\n_, err := p.getUpdates(ctx, marker)\nif err != nil {\n\tif ctx.Err() != nil {\n\t\treturn // shutting down\n\t}\n\tslog.Warn(\"max: poll failed, backing off\", \"err\", err)\n\ttime.Sleep(backoff)\n\tcontinue\n}","handlingStrategy":"retry","validationCode":"// Pre-flight reachability check before starting the poll loop\nfunc apiReachable(client *http.Client, apiBase string) error {\n\tresp, err := client.Get(apiBase)\n\tif err != nil { return err }\n\tresp.Body.Close()\n\treturn nil\n}","typeGuard":"func isContextCanceled(err error) bool { return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) }","tryCatchPattern":"for {\n\tupdates, err := p.getUpdates(ctx, marker)\n\tif err != nil {\n\t\tif isContextCanceled(err) { return } // clean shutdown\n\t\tslog.Warn(\"max: poll request failed\", \"err\", err)\n\t\tbackoff = min(backoff*2, maxBackoff)\n\t\tselect { case <-ctx.Done(): return; case <-time.After(backoff): }\n\t\tcontinue\n\t}\n\tbackoff = initialBackoff\n\t// process updates...\n}","preventionTips":["Use a poll HTTP client whose Timeout comfortably exceeds the server's long-poll hold.","Always check ctx.Err() on poll errors to keep shutdown quiet.","Back off exponentially on consecutive transport failures.","Monitor DNS/proxy reachability of botapi.max.ru from the host running the bot."],"tags":["network","polling","long-poll","max"],"backgroundTag":"network-request-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}