Netflix/chaosmonkey · warning

failed to close response body from %s

Error message

failed to close response body from %s

What it means

This deferred error fires when resp.Body.Close() fails inside OtherID, but only when the rest of OtherID completed without error (cerr != nil && err == nil). The GET for the instance's alternate ID otherwise succeeded; only connection teardown failed. Because OtherID uses a named return, this close error replaces the successful result with an error, which then surfaces wrapped as "retrieve other id failed" in Execute.

Source

Thrown at spinnaker/terminator.go:152

		log.Fatalf("chronos.jsonPayload could not marshal data into json: %v", err)
	}

	return result
}

// OtherID returns the alternate instance id of an instance, if it exists
// If there is no alternate instance id, it returns an empty string
// This is used by Titus, where we also report the uuid
func (s Spinnaker) OtherID(ins chaosmonkey.Instance) (otherID string, err error) {
	url := s.instanceURL(ins.AccountName(), ins.RegionName(), ins.ID())
	resp, err := s.client.Get(url)
	if err != nil {
		return "", errors.Wrap(err, fmt.Sprintf("get failed on %s", url))
	}

	defer func() {
		if cerr := resp.Body.Close(); cerr != nil && err == nil {
			err = errors.Wrap(cerr, fmt.Sprintf("failed to close response body from %s", url))
		}
	}()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", errors.Wrap(err, fmt.Sprintf("body read failed at %s", url))
	}

	// Example of response body:
	/*
		{
			...
			"health": [
				{
					"type": "Titus",
					"healthClass": "platform",
					"state": "Up"
				},

View on GitHub (pinned to eaa28fb761)

Solutions

  1. Check whether the underlying data was still correct; if so, this is a teardown artifact — drain the body before closing to reduce it.
  2. Use io.Copy(io.Discard, resp.Body) before Close, or ensure full reads (the code already does ioutil.ReadAll, so check for early-return paths).
  3. Tune http.Transport keepalive settings (IdleConnTimeout, MaxIdleConnsPerHost) to avoid stale connections.
  4. Correlate errors.Cause with LB/Gate logs for connection resets.
  5. If it persists and blocks terminations, consider ignoring close errors on GET responses whose body was fully read.

Example fix

// before
if cerr := resp.Body.Close(); cerr != nil && err == nil {
	err = errors.Wrap(cerr, fmt.Sprintf("failed to close response body from %s", url))
}
// after
// body fully read above; treat close error as non-fatal
_ = resp.Body.Close()
Defensive patterns

Strategy: try-catch

Validate before calling

// body is fully read via ioutil.ReadAll before close; drain defensively on early-return paths
io.Copy(io.Discard, resp.Body)

Try / catch

otherID, err := spinnaker.OtherID(ins)
if err != nil {
	if strings.Contains(err.Error(), "failed to close response body") {
		log.Printf("non-fatal close error looking up %s: %v", ins.ID(), err)
		// data was fine; retry or proceed per policy
	}
	return errors.Wrap(err, "retrieve other id failed")
}

Prevention

When it happens

Trigger: GET to the Spinnaker instance endpoint succeeded and the body was read, but resp.Body.Close() returned an error — typically a reset/aborted keepalive connection during teardown.

Common situations: Server or LB closing keepalive connections aggressively; stale pooled connections in http.Transport; network device interrupting idle connections between request completion and close.

Related errors


AI-assisted analysis of Netflix/chaosmonkey@eaa28fb761 (2026-09-03). Data as JSON: /api/errors/dadaf6bd858679a7. Report an issue: GitHub.