{"record":{"id":"ed9d41ffe4edddd0","repo":"chenhg5/cc-connect","slug":"poll-http-d-s","errorCode":null,"errorMessage":"poll: HTTP %d: %s","messagePattern":"poll: HTTP (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":923,"sourceCode":"\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\n}\n\nfunc (p *Platform) handleUpdate(ctx context.Context, upd *maxUpdate) {\n\tswitch upd.UpdateType {\n\tcase \"message_created\":\n\t\tif upd.Message != nil {","sourceCodeStart":905,"sourceCodeEnd":941,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L905-L941","documentation":"The MAX /updates long-poll endpoint answered with a non-200 HTTP status. The platform reads up to 512 bytes of the body and reports \"poll: HTTP <status>: <body>\". Since this is the receive path, a persistent non-200 (401 unauthorized, 403 forbidden, 5xx server error) stops the bot from receiving updates entirely.","triggerScenarios":"The polling loop's getUpdates call receiving 401 (invalid/expired bot token), 403 (bot blocked or insufficient scope), 404 (wrong apiBase path), 429 (polling too aggressively), or 500/503 (MAX server incident) — with the status passing the != http.StatusOK check after a successful transport round-trip.","commonSituations":"Rotated or revoked bot access token not updated in config.toml; wrong apiBase (e.g. pointing at the wrong MAX environment); tight polling loops triggering 429; MAX outages; regional blocking of the API host.","solutions":["Read the embedded body in the error — it states MAX's reason (auth vs. permission vs. rate limit).","For 401, update the bot token in config.toml and restart; verify it with a simple authorized API call.","For 429, add backoff and honor any Retry-After header before the next poll instead of looping at full speed.","For 5xx, retry with exponential backoff — these are transient MAX-side incidents.","Confirm apiBase matches the current documented MAX Bot API URL and version."],"exampleFix":"// before\nif resp.StatusCode != http.StatusOK {\n\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\treturn nil, fmt.Errorf(\"poll: HTTP %d: %s\", resp.StatusCode, body)\n}\n// after: caller honors Retry-After on 429\nupdates, err := p.getUpdates(ctx, marker)\nif err != nil {\n\tvar httpErr *maxHTTPError\n\tif errors.As(err, &httpErr) && httpErr.Status == http.StatusTooManyRequests {\n\t\tra := httpErr.RetryAfter\n\t\tif ra == 0 { ra = 5 * time.Second }\n\t\tselect { case <-ctx.Done(): return; case <-time.After(ra): }\n\t\tcontinue\n\t}\n}","handlingStrategy":"retry","validationCode":"// Validate the token and endpoint before polling\nfunc pollConfigOK(apiBase, token string) error {\n\tif token == \"\" { return fmt.Errorf(\"missing max bot token\") }\n\tu, err := neturl.Parse(apiBase)\n\tif err != nil || u.Host == \"\" { return fmt.Errorf(\"invalid apiBase: %q\", apiBase) }\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"updates, err := p.getUpdates(ctx, marker)\nif err != nil {\n\tvar he *maxHTTPError // parse status out of \"poll: HTTP %d\"\n\tif errors.As(err, &he) {\n\t\tswitch {\n\t\tcase he.Status == 401:\n\t\t\treturn fmt.Errorf(\"max: token rejected, fix config: %w\", err) // do not hot-loop on auth\n\t\tcase he.Status == 429:\n\t\t\twait := he.RetryAfter; if wait == 0 { wait = 5 * time.Second }\n\t\t\ttime.Sleep(wait); continue\n\t\tcase he.Status >= 500:\n\t\t\ttime.Sleep(backoff); continue\n\t\t}\n\t}\n}","preventionTips":["Never hot-loop on 401/403 — abort and surface a config error instead.","Back off on 429 and honor Retry-After before the next poll.","Keep the bot token fresh in config.toml; alert before expiry.","Watch MAX status/incident channels for 5xx polling outages; keep apiBase current."],"tags":["http","polling","api","max"],"backgroundTag":"http-error-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}