{"record":{"id":"39dc6681b31d079f","repo":"chenhg5/cc-connect","slug":"poll-decode-w","errorCode":null,"errorMessage":"poll decode: %w","messagePattern":"poll decode: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":928,"sourceCode":"\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 {\n\t\t\tp.handleMessage(ctx, upd.Message)\n\t\t}\n\tcase \"message_callback\":\n\t\tif upd.Callback != nil {\n\t\t\tp.handleCallback(ctx, upd.Callback)","sourceCodeStart":910,"sourceCodeEnd":946,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L910-L946","documentation":"The MAX Bot API long-poll response body could not be decoded as the expected maxUpdatesResponse JSON. The platform treats any JSON decode failure during polling as a hard error wrapping the underlying cause (%w), because the poll loop cannot progress without a valid updates payload.","triggerScenarios":"p.client.Do succeeded with HTTP 200 but the body is not valid JSON: truncated response, HTML error page behind a proxy, wrong apiBase pointing at a non-MAX server, or MAX changing the response schema so fields fail to unmarshal.","commonSituations":"Corporate proxy or captive portal returning HTML for 200 responses; misconfigured apiBase in config.toml (e.g. pointed at a local mock); MAX API schema change on a new undocumented field type; network interruption mid-body.","solutions":["Log the raw body (read resp.Body before decoding) to see what was actually returned","Verify apiBase in config.toml points at the correct MAX Bot API URL","Check for proxies/captive portals returning HTML with 200 status","Confirm the MAX API version; check result.Updates still unmarshals with the current struct tags","Add retry/backoff around poll so transient truncation does not kill the session"],"exampleFix":"// before\nvar result maxUpdatesResponse\nif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\treturn nil, fmt.Errorf(\"poll decode: %w\", err)\n}\n// after\nraw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\nvar result maxUpdatesResponse\nif err := json.Unmarshal(raw, &result); err != nil {\n\treturn nil, fmt.Errorf(\"poll decode: %w (body: %.256s)\", err, raw)\n}","handlingStrategy":"retry","validationCode":"if resp.StatusCode == 200 && !json.Valid(rawBody) { /* skip and re-poll */ }","typeGuard":null,"tryCatchPattern":"result, err := pollOnce(ctx)\nif err != nil {\n\tvar jerr *json.SyntaxError\n\tif errors.As(err, &jerr) { logRawBody(); continue } // transient bad body\n\treturn err\n}","preventionTips":["Pin apiBase to the documented MAX endpoint","Alert on repeated poll decode failures (proxy/HTML injection)","Keep the updates struct tags in sync with the MAX API changelog"],"tags":["network","json","polling","bot-api"],"backgroundTag":"json-unmarshal-failed","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"}