Netflix/chaosmonkey · warning
body close failed at %s
Error message
body close failed at %s
What it means
This error is produced by the deferred cleanup in Spinnaker.GetInstanceIDs when resp.Body.Close() returns an error after a successful HTTP GET. The library wraps it with the URL so you know which response body could not be closed. Note a quirk: errors.Wrapf(err, ...) wraps the outer err (nil at that point conceptually) rather than cerr, so the close error itself can be lost — but the message still flags that closing failed.
Source
Thrown at spinnaker/spinnaker.go:264
continue
}
c <- app
}
}
// GetInstanceIDs gets the instance ids for a cluster
func (s Spinnaker) GetInstanceIDs(app string, account D.AccountName, cloudProvider string, region D.RegionName, cluster D.ClusterName) (D.ASGName, []D.InstanceID, error) {
url := s.activeASGURL(app, string(account), string(cluster), cloudProvider, string(region))
resp, err := s.client.Get(url)
if err != nil {
return "", nil, errors.Wrapf(err, "http get failed at %s", url)
}
defer func() {
if cerr := resp.Body.Close(); cerr != nil && err == nil {
err = errors.Wrapf(err, "body close failed at %s", url)
}
}()
if resp.StatusCode != http.StatusOK {
return "", nil, errors.Errorf("unexpected response code (%d) from %s", resp.StatusCode, url)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", nil, errors.Wrap(err, fmt.Sprintf("body read failed at %s", url))
}
var data struct {
Name string
Instances []struct{ Name string }
}
err = json.Unmarshal(body, &data)View on GitHub (pinned to eaa28fb761)
Solutions
- Read the full body (ioutil.ReadAll) before the deferred close runs.
- Check for concurrent use of the same http.Client/connection; share the client but not responses across goroutines.
- Retry the request; body-close failures after keep-alive races are usually transient.
- Upgrade the Go version / net/http usage if hitting known keep-alive close bugs.
Example fix
// before
defer func() {
if cerr := resp.Body.Close(); cerr != nil && err == nil {
err = errors.Wrapf(err, "body close failed at %s", url)
}
}()
// after
defer func() {
if cerr := resp.Body.Close(); cerr != nil && err == nil {
err = errors.Wrapf(cerr, "body close failed at %s", url) // wrap cerr, not err
}
}()
io.Copy(ioutil.Discard, resp.Body) // drain body before close Defensive patterns
Strategy: try-catch
Try / catch
// Go: inspect the wrapped error; treat as transient
asg, ids, err := sp.GetInstanceIDs(app, acct, cp, region, cluster)
if err != nil {
if strings.Contains(err.Error(), "body close failed") {
// usually transient; log and retry once
log.Printf("transient body-close failure, retrying: %v", err)
return sp.GetInstanceIDs(app, acct, cp, region, cluster)
}
return err
} Prevention
- Always fully read or drain the response body before close
- Share http.Client across goroutines but never responses
- Keep Go's net/http up to date
- Log full error chains to spot recurring close failures
When it happens
Trigger: Calling GetInstanceIDs when the response was received but resp.Body.Close() returns a non-nil error — typically after the connection was reused/aborted, or the body was never fully read before close, or a keep-alive connection was closed unexpectedly.
Common situations: Server closes keep-alive connections mid-flight; reading was skipped or incomplete before close; Go http client connection-reuse races under concurrency.
Related errors
- failed to close response body of %s
- failed to close response body from %s
- http get failed at %s
- unexpected response code (%d) from %s
- body read failed at %s
AI-assisted analysis of Netflix/chaosmonkey@eaa28fb761 (2026-09-03).
Data as JSON: /api/errors/66064afa74afd81e.
Report an issue: GitHub.