hashicorp/terraform · error

cannot read

Error message

cannot read %s: %s

What it means

Thrown by CredentialsSource.updateLocalHostCredentials when ioutil.ReadFile fails to read the credentials JSON file (e.g. ~/.terraform.d/credentials.tfrc.json) with an error other than os.IsNotExist. It is the first I/O gate in updating a host's credentials: any read error that is not 'file does not exist' (permission denied, I/O error, path-too-long) is surfaced here before parsing.

Solutions

  1. Verify the path Terraform resolves for the credentials file: run `terraform -help` style debugging or print s.CredentialsFilePath() — usually ~/.terraform.d/credentials.tfrc.json or $TF_CLI_CONFIG_FILE/credentials.tfrc.json.
  2. Check permissions/ownership: `ls -l <path>` and `stat <path>`; ensure the current uid can read it (chmod u+r or chown).
  3. Confirm the path is a regular file, not a directory or broken symlink (`readlink -f <path>`).
  4. If the file is corrupt/unwanted, back it up and remove it so Terraform recreates it on next login.

Example fix

// before: file owned by root, user cannot read
// $ sudo chown $USER:$USER ~/.terraform.d/credentials.tfrc.json
// $ chmod 600 ~/.terraform.d/credentials.tfrc.json
// after: terraform login succeeds and updateLocalHostCredentials reads the file
Defensive patterns

Strategy: validation

Validate before calling

// Before calling any credentials-updating path, confirm the file is readable
func credentialsFileReadable(path string) error {
    fi, err := os.Stat(path)
    if os.IsNotExist(err) {
        return nil // not-exist is tolerated by updateLocalHostCredentials
    }
    if err != nil {
        return fmt.Errorf("stat %s: %w", path, err)
    }
    if fi.IsDir() {
        return fmt.Errorf("%s is a directory, not a credentials file", path)
    }
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("%s not readable: %w", path, err)
    }
    f.Close()
    return nil
}

Prevention

When it happens

Trigger: Calling any Terraform command that persists credentials (terraform login / logout, or the credentials helper update path) when the credentials file exists but is unreadable. Specifically: file mode bits deny read to the current uid; the path points to a directory; the volume is detached; or the file is on a network mount returning EIO.

Common situations: File created by a different user/root with 0600 perms so the running user cannot read it; TF_CLI_CONFIG_FILE or TF_CREDENTIALS pointing at a stale path; a previous crash left a half-named file; container volume mount permission mismatch.

Related errors


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

Appendix: source

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

	default:
		// Should never happen because the above cases are exhaustive
		return fmt.Errorf("invalid credentials location %#v", loc)
	}
}

func (s *CredentialsSource) updateLocalHostCredentials(host svchost.Hostname, new svcauth.HostCredentialsWritable) error {
	// This function updates the local credentials file in particular,
	// regardless of whether a credentials helper is active. It should be
	// called only indirectly via updateHostCredentials.

	filename, err := s.CredentialsFilePath()
	if err != nil {
		return fmt.Errorf("unable to determine credentials file path: %s", err)
	}

	oldSrc, err := ioutil.ReadFile(filename)
	if err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("cannot read %s: %s", filename, err)
	}

	var raw map[string]interface{}

	if len(oldSrc) > 0 {
		// When decoding we use a custom decoder so we can decode any numbers as
		// 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"]

View on GitHub (pinned to d32a084675)