{"record":{"id":"66064afa74afd81e","repo":"Netflix/chaosmonkey","slug":"body-close-failed-at-s","errorCode":null,"errorMessage":"body close failed at %s","messagePattern":"body close failed at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"spinnaker/spinnaker.go","lineNumber":264,"sourceCode":"\t\t\tcontinue\n\t\t}\n\n\t\tc <- app\n\t}\n}\n\n// GetInstanceIDs gets the instance ids for a cluster\nfunc (s Spinnaker) GetInstanceIDs(app string, account D.AccountName, cloudProvider string, region D.RegionName, cluster D.ClusterName) (D.ASGName, []D.InstanceID, error) {\n\turl := s.activeASGURL(app, string(account), string(cluster), cloudProvider, string(region))\n\n\tresp, err := s.client.Get(url)\n\tif err != nil {\n\t\treturn \"\", nil, errors.Wrapf(err, \"http get failed at %s\", url)\n\t}\n\n\tdefer func() {\n\t\tif cerr := resp.Body.Close(); cerr != nil && err == nil {\n\t\t\terr = errors.Wrapf(err, \"body close failed at %s\", url)\n\t\t}\n\t}()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", nil, errors.Errorf(\"unexpected response code (%d) from %s\", resp.StatusCode, url)\n\t}\n\n\tbody, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", nil, errors.Wrap(err, fmt.Sprintf(\"body read failed at %s\", url))\n\t}\n\n\tvar data struct {\n\t\tName      string\n\t\tInstances []struct{ Name string }\n\t}\n\n\terr = json.Unmarshal(body, &data)","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/Netflix/chaosmonkey/blob/eaa28fb761c0ebe8644d1333e5d164e9cc3071e9/spinnaker/spinnaker.go#L246-L282","documentation":"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.","triggerScenarios":"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.","commonSituations":"Server closes keep-alive connections mid-flight; reading was skipped or incomplete before close; Go http client connection-reuse races under concurrency.","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."],"exampleFix":"// before\ndefer func() {\n    if cerr := resp.Body.Close(); cerr != nil && err == nil {\n        err = errors.Wrapf(err, \"body close failed at %s\", url)\n    }\n}()\n// after\ndefer func() {\n    if cerr := resp.Body.Close(); cerr != nil && err == nil {\n        err = errors.Wrapf(cerr, \"body close failed at %s\", url) // wrap cerr, not err\n    }\n}()\nio.Copy(ioutil.Discard, resp.Body) // drain body before close","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Go: inspect the wrapped error; treat as transient\nasg, ids, err := sp.GetInstanceIDs(app, acct, cp, region, cluster)\nif err != nil {\n    if strings.Contains(err.Error(), \"body close failed\") {\n        // usually transient; log and retry once\n        log.Printf(\"transient body-close failure, retrying: %v\", err)\n        return sp.GetInstanceIDs(app, acct, cp, region, cluster)\n    }\n    return err\n}","preventionTips":["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"],"tags":["http","go","resource-cleanup","spinnaker"],"backgroundTag":"response-body-close-failed","analyzedSha":"eaa28fb761c0ebe8644d1333e5d164e9cc3071e9","analyzedAt":"2026-09-03T17:04:39.020Z","contentChangedAt":"2026-09-03T17:04:39.020Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}