hashicorp/terraform · error

credentials file has invalid value for "credentials"…

Error message

credentials file %s has invalid value for "credentials" property: must be a JSON object

What it means

Thrown after the credentials file parsed as JSON but its top-level "credentials" property is not a JSON object (map). Terraform requires `credentials` to be an object keyed by hostname. If it is an array, string, number, or null, the type assertion `rawCredsI.(map[string]interface{})` fails and this error fires before any mutation.

Solutions

  1. Inspect the file: `jq .type ~/.terraform.d/credentials.tfrc.json` and `jq '.credentials | type'` — it must report `object`.
  2. Rewrite the credentials property as an object, e.g. `{"credentials": {"app.terraform.io": {"token": "..."}}}`.
  3. If unsure of the correct shape, back up the file and run `terraform login` to regenerate it.
  4. Audit any external tool or helper writing the file against the documented schema.

Example fix

// before (invalid):
//   { "credentials": "abc123" }
// after (valid):
//   {
//     "credentials": {
//       "app.terraform.io": { "token": "abc123" }
//     }
//   }
Defensive patterns

Strategy: type-guard

Type guard

// Guard that the parsed credentials file has the expected object shape
func credentialsShapeOK(path string) error {
    var raw map[string]interface{}
    data, err := os.ReadFile(path)
    if err != nil { return err }
    if err := json.Unmarshal(data, &raw); err != nil { return err }
    c, ok := raw["credentials"]
    if !ok { return nil } // missing is fine — code creates it
    if _, ok := c.(map[string]interface{}); !ok {
        return fmt.Errorf("credentials property must be a JSON object")
    }
    return nil
}

Prevention

When it happens

Trigger: Credentials file is valid JSON but has shape like `{"credentials": "..."}`, `{"credentials": [ ... ]}`, or `{"credentials": 123}`. Typically from a hand edit, a broken migration, or a tool that wrote a different schema.

Common situations: User confused the legacy `~/.terraformrc` (which stores helper config differently) with `credentials.tfrc.json`; a credentials helper emitted the wrong top-level shape; a copy-paste from documentation that used the wrong key.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/88cd2c47f5e87ebe. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/cliconfig/credentials.go:358

		// json.Number and thus avoid losing any accuracy in our round-trip.
		dec := json.NewDecoder(bytes.NewReader(oldSrc))
		dec.UseNumber()
		err = dec.Decode(&raw)
		if err != nil {
			return fmt.Errorf("cannot read %s: %s", filename, err)
		}
	} else {
		raw = make(map[string]interface{})
	}

	rawCredsI, ok := raw["credentials"]
	if !ok {
		rawCredsI = make(map[string]interface{})
		raw["credentials"] = rawCredsI
	}
	rawCredsMap, ok := rawCredsI.(map[string]interface{})
	if !ok {
		return fmt.Errorf("credentials file %s has invalid value for \"credentials\" property: must be a JSON object", filename)
	}

	// We use display-oriented hostnames in our file to mimick how a human user
	// would write it, so we need to search for and remove any key that
	// normalizes to our target hostname so we won't generate something invalid
	// when the existing entry is slightly different.
	for givenHost := range rawCredsMap {
		canonHost, err := svchost.ForComparison(givenHost)
		if err == nil && canonHost == host {
			delete(rawCredsMap, givenHost)
		}
	}

	// If we have a new object to store we'll write it in now. If the previous
	// object had the hostname written in a different way then this will
	// appear to change it into our canonical display form, with all the
	// letters in lowercase and other transforms from the Internationalized
	// Domain Names specification.

View on GitHub (pinned to d32a084675)