Netflix/chaosmonkey · error

json unmarshal failed, body: %s

Error message

json unmarshal failed, body: %s

What it means

OtherID throws this when json.Unmarshal cannot decode the instance endpoint response into the expected struct with Health and Error fields. The request completed and the body was read, but the body is not the expected JSON document. The failing body is embedded verbatim in the message so you can see exactly what Spinnaker returned.

Source

Thrown at spinnaker/terminator.go:187

					"state": "Up"
				},
				{
					"instanceId": "55fe33ab-5b66-450a-85f7-f3129806b87f",
					"titusTaskId": "Titus-123456-worker-0-0",
					...
				}
			],
		}
	*/

	var fields struct {
		Health []map[string]interface{} `json:"health"`
		Error  string                   `json:"error"`
	}

	err = json.Unmarshal(body, &fields)
	if err != nil {
		return "", errors.Wrap(err, fmt.Sprintf("json unmarshal failed, body: %s", body))
	}

	if resp.StatusCode != http.StatusOK {
		if fields.Error == "" {
			return "", fmt.Errorf("unexpected status code: %d. body: %s", resp.StatusCode, body)
		}

		return "", fmt.Errorf("unexpected status code: %d. error: %s", resp.StatusCode, fields.Error)
	}

	// In some cases, an instance may be missing health information.
	// We just return a blank otherID in that case
	if len(fields.Health) < 2 {
		return "", nil
	}

	otherID, ok := fields.Health[1]["instanceId"].(string)
	if !ok {

View on GitHub (pinned to eaa28fb761)

Solutions

  1. Inspect the body embedded in the error message to see what was actually returned.
  2. Curl the same URL with the same credentials to reproduce and inspect headers (content-type, content-encoding).
  3. Check whether the instance still exists in Spinnaker/Titus; a terminated instance may yield a non-JSON error page.
  4. Verify no auth/SSO proxy is intercepting the request and that the endpoint URL targets Gate's instance API.
  5. Confirm your Spinnaker version's instance response matches the library's expected schema and update the library or Spinnaker accordingly.
Defensive patterns

Strategy: validation

Validate before calling

resp, err := client.Get(instanceURL)
if err != nil {
	return err
}
body, _ := ioutil.ReadAll(resp.Body)
if !json.Valid(body) {
	return fmt.Errorf("non-JSON from %s (content-type: %s): %.200s", instanceURL, resp.Header.Get("Content-Type"), body)
}
var probe struct{ Health []json.RawMessage }
if err := json.Unmarshal(body, &probe); err != nil {
	return fmt.Errorf("unexpected instance schema: %v", err)
}

Type guard

func looksLikeInstanceDoc(body []byte) bool {
	var probe struct{ Health []map[string]interface{} }
	return json.Unmarshal(body, &probe) == nil
}

Try / catch

err = json.Unmarshal(body, &fields)
if err != nil {
	log.Printf("json unmarshal failed for %s, body: %.500s", url, body)
	return "", errors.Wrap(err, fmt.Sprintf("json unmarshal failed, body: %s", body))
}

Prevention

When it happens

Trigger: GET <spinnaker-api>/<account>/<region>/<instanceId> returns a body that is not valid JSON for the expected schema: an HTML error/login page from a proxy, an empty body, gzip/encoding garbage, or a Spinnaker response whose instance payload differs from the expected {health: [...]} shape.

Common situations: Instance already terminated so Gate returns an unexpected document; an SSO/auth proxy intercepting the GET with its own HTML response; content-encoding mismatches (client not handling gzip); Spinnaker version changes altering the instance response schema; wrong endpoint URL hitting a non-Gate service.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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