hashicorp/terraform · error

Failed to load state

Error message

Failed to load state: %s

What it means

In Meta.backendFromState (used only for `terraform init -backend=false`), RefreshState on the local backend-cache file failed. Unlike 661 this path returns a local backend when the file is absent (nil state is fine), so this error specifically means the file exists but cannot be read/parsed.

Solutions

  1. Read the inner %s for the parse/IO cause.
  2. Validate .terraform/terraform.tfstate is well-formed JSON; if corrupt, back it up, delete it, and re-init.
  3. Correct ownership/permissions on .terraform so the current user can read it.
  4. Re-run `tofu init -backend=false` after cleanup; a missing file is acceptable and yields a fresh local backend.

Example fix

// before
tofu init -backend=false  # .terraform/terraform.tfstate corrupt
# -> Failed to load state

// after
rm .terraform/terraform.tfstate
tofu init -backend=false
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate the cache file before a -backend=false run.
func validateBackendCacheReadable(dataDir string) error {
    p := filepath.Join(dataDir, DefaultStateFilename)
    if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) { return nil }
    b, err := os.ReadFile(p)
    if err != nil { return err }
    var raw json.RawMessage
    return json.Unmarshal(b, &raw)
}

Try / catch

if err := sMgr.RefreshState(); err != nil {
    if errors.Is(err, os.ErrNotExist) {
        // nil state is fine for backendFromState
    } else {
        diags = diags.Append(fmt.Errorf("Failed to load state (cache unreadable; run 'tofu init -reconfigure'): %s", err))
        return nil, diags
    }
}

Prevention

When it happens

Trigger: sMgr.RefreshState() errors on the existing .terraform/terraform.tfstate during a -backend=false run. Triggers: corrupt JSON in the cache file, truncated write, permission denied, or an incompatible/unknown state snapshot version that the parser rejects.

Common situations: Running `tofu init -backend=false` (or any command forcing local backend) against a working dir whose cache file was corrupted by a crash, manual edit, or a different CLI version; read-only mount of .terraform; file owned by another user.

Related errors


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

Appendix: source

Thrown at internal/command/meta_backend.go:1493

		"Unable to determine state store init reason",
		"This is a bug in Terraform and should be reported",
	))
	return nil, diags
}

// backendFromState returns the initialized (not configured) backend directly
// from the backend state. This should be used only when a user runs
// `terraform init -backend=false`. This function returns a local backend if
// there is no backend state or no backend configured.
func (m *Meta) backendFromState(_ context.Context) (backend.Backend, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	// Get the path to where we store a local cache of backend configuration
	// if we're using a remote backend. This may not yet exist which means
	// we haven't used a non-local backend before. That is okay.
	statePath := filepath.Join(m.DataDir(), DefaultStateFilename)
	sMgr := &clistate.LocalState{Path: statePath}
	if err := sMgr.RefreshState(); err != nil {
		diags = diags.Append(fmt.Errorf("Failed to load state: %s", err))
		return nil, diags
	}
	s := sMgr.State()
	if s == nil {
		// no state, so return a local backend
		log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory")
		return backendLocal.New(), diags
	}

	// Depending on the contents of the backend state file,
	// prepare a backend.Backend in the appropriate way.
	var b backend.Backend
	switch {
	case !s.StateStore.Empty():
		// state_store
		log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q state store", s.StateStore.Type)
		var ssDiags tfdiags.Diagnostics
		b, ssDiags = m.savedStateStore(sMgr) // Relies on the state manager's internal state being refreshed above.

View on GitHub (pinned to d32a084675)