{"record":{"id":"2b81bf48f516a5bc","repo":"wtfutil/wtf","slug":"failed-to-read-top-clients-response-s","errorCode":null,"errorMessage":" failed to read top clients response\n %s","messagePattern":" failed to read top clients response\n (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/pihole/client.go","lineNumber":225,"sourceCode":"\tif resp, err = c.Do(req); err != nil || resp == nil {\n\t\treturn tc, fmt.Errorf(\" failed to connect to Pi-hole server\\n %s\", parseError(err))\n\t}\n\n\tdefer func() {\n\t\tif closeErr := resp.Body.Close(); closeErr != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tif resp.StatusCode >= http.StatusBadRequest {\n\t\treturn tc, fmt.Errorf(\" failed to retrieve version from Pi-hole server\\n http status code: %d\",\n\t\t\tresp.StatusCode)\n\t}\n\n\tvar rBody []byte\n\n\tif rBody, err = io.ReadAll(resp.Body); err != nil {\n\t\treturn tc, fmt.Errorf(\" failed to read top clients response\\n %s\", parseError(err))\n\t}\n\n\tif err = json.Unmarshal(rBody, &tc); err != nil {\n\t\treturn tc, fmt.Errorf(\" failed to retrieve top clients: check provided api URL and token\\n %s\",\n\t\t\tparseError(err))\n\t}\n\n\treturn tc, err\n}\n\ntype QueryTypes struct {\n\tQueryTypes map[string]float32 `json:\"querytypes\"`\n}\n\nfunc getQueryTypes(c http.Client, settings *Settings) (qt QueryTypes, err error) {\n\tvar req *http.Request\n\n\tvar url *url2.URL","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/wtfutil/wtf/blob/bb838c1ccb0f0f3223690df44afdec663d622881/modules/pihole/client.go#L207-L243","documentation":"getTopClients reads the entire response body with io.ReadAll before JSON decoding. This error is thrown if reading the body fails, e.g. the connection was closed mid-response, a read deadline/timed out, or the server aborted the transfer. parseError wraps the underlying io error with the auth token redacted.","triggerScenarios":"io.ReadAll(resp.Body) returns an error during the topClients API call: server closes connection mid-body, context/timeout deadline exceeded while streaming, chunked-encoding corruption, or proxy interruption.","commonSituations":"Flaky Wi-Fi/network to the Pi-hole, an http.Client Timeout too short for a slow Pi-hole under heavy query load, intermediary proxy dropping long responses, or Pi-hole restarting during the request.","solutions":["Retry the request; transient connection resets often succeed on a second attempt","Increase or remove the http.Client Timeout if Pi-hole responses are slow under load","Check network stability between the client and Pi-hole (packet loss, VPN, Docker NAT)","Verify no proxy/LB is cutting the response early","Look at the wrapped parseError message for the exact I/O cause (e.g. context deadline exceeded, unexpected EOF)"],"exampleFix":"// before\nc := http.Client{Timeout: 500 * time.Millisecond}\n\n// after\nc := http.Client{Timeout: 10 * time.Second}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"tc, err := getTopClients(client, settings)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to read\") {\n        // transient I/O: bounded exponential backoff\n        for i := 0; i < 3; i++ {\n            time.Sleep(time.Duration(1<<i) * time.Second)\n            if tc, err = getTopClients(client, settings); err == nil { break }\n        }\n    }\n    return err\n}","preventionTips":["Give the http.Client a Timeout that comfortably exceeds Pi-hole's slowest responses","Monitor network stability to the Pi-hole host (packet loss, VPN MTU)","Avoid proxies/LBs with short idle timeouts in front of Pi-hole","Use keep-alive connections to reduce mid-body resets"],"tags":["io","network","http-client","go","pihole"],"backgroundTag":"response-read-failed","analyzedSha":"bb838c1ccb0f0f3223690df44afdec663d622881","analyzedAt":"2026-09-03T17:02:45.030Z","contentChangedAt":"2026-09-03T17:02:45.030Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}