wtfutil/wtf · error
failed to read top clients response %s
Error message
failed to read top clients response %s
What it means
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.
Source
Thrown at modules/pihole/client.go:225
if resp, err = c.Do(req); err != nil || resp == nil {
return tc, fmt.Errorf(" failed to connect to Pi-hole server\n %s", parseError(err))
}
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
return
}
}()
if resp.StatusCode >= http.StatusBadRequest {
return tc, fmt.Errorf(" failed to retrieve version from Pi-hole server\n http status code: %d",
resp.StatusCode)
}
var rBody []byte
if rBody, err = io.ReadAll(resp.Body); err != nil {
return tc, fmt.Errorf(" failed to read top clients response\n %s", parseError(err))
}
if err = json.Unmarshal(rBody, &tc); err != nil {
return tc, fmt.Errorf(" failed to retrieve top clients: check provided api URL and token\n %s",
parseError(err))
}
return tc, err
}
type QueryTypes struct {
QueryTypes map[string]float32 `json:"querytypes"`
}
func getQueryTypes(c http.Client, settings *Settings) (qt QueryTypes, err error) {
var req *http.Request
var url *url2.URLView on GitHub (pinned to bb838c1ccb)
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)
Example fix
// before
c := http.Client{Timeout: 500 * time.Millisecond}
// after
c := http.Client{Timeout: 10 * time.Second} Defensive patterns
Strategy: retry
Try / catch
tc, err := getTopClients(client, settings)
if err != nil {
if strings.Contains(err.Error(), "failed to read") {
// transient I/O: bounded exponential backoff
for i := 0; i < 3; i++ {
time.Sleep(time.Duration(1<<i) * time.Second)
if tc, err = getTopClients(client, settings); err == nil { break }
}
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to execute Azure query: %w
- executing request: %w
- unexpected status %d from DEV.to API
- could not get docker system info: %w
- could not get container list: %w
AI-assisted analysis of wtfutil/wtf@bb838c1ccb (2026-09-03).
Data as JSON: /api/errors/2b81bf48f516a5bc.
Report an issue: GitHub.