hashicorp/terraform · error

error loading state

Error message

error loading state: %w

What it means

Thrown by Local.localRun when b.StateMgr(op.Workspace) returns diagnostics with errors. StateMgr is responsible for creating/opening the state manager for the named workspace, so this error means Terraform could not even obtain a handle to the state before trying to read it.

Solutions

  1. Inspect the wrapped sDiags.Err() for the real cause (it is included via %w).
  2. Ensure the state path parent directory exists and is writable: `mkdir -p .terraform/tfstate` and `chmod u+w` on TF_DATA_DIR.
  3. Verify the workspace name is valid (no path separators, not empty).
  4. If a remote backend is wrapped behind Local, fix the remote connection (creds, endpoint) before retrying.

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the state path is usable before invoking the local run.
func statePathUsable(dir string) error {
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return fmt.Errorf("cannot create state dir %s: %w", dir, err)
    }
    probe := filepath.Join(dir, ".writeprobe")
    if err := os.WriteFile(probe, []byte{}, 0o644); err != nil {
        return fmt.Errorf("state dir %s not writable: %w", dir, err)
    }
    _ = os.Remove(probe)
    return nil
}

Type guard

null

Try / catch

_, stateMgr, diags := b.LocalRun(ctx, op)
if diags.HasErrors() {
    if strings.Contains(diags.Err().Error(), "error loading state") {
        // surface a hint about state path / permissions
    }
    return diags
}

Prevention

When it happens

Trigger: b.StateMgr fails inside createState (cannot create the state file path) or, when the Local backend wraps another backend, the wrapped backend's StateMgr returns errors. HasErrors() is true on the returned sDiags, and the whole diagnostics error is wrapped with %w.

Common situations: The state directory does not exist and cannot be created (permission denied on parent), the workspace name is invalid, or a remote backend wrapped behind Local is unreachable. Also seen when TF_DATA_DIR points to a read-only location.

Related errors


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

Appendix: source

Thrown at internal/backend/local/backend_local.go:53

	// seems bad but we're preserving it for now until we have time to
	// properly design this API, vs. just preserving whatever it currently
	// happens to do.
	op.Type = backendrun.OperationTypeInvalid

	op.StateLocker = op.StateLocker.WithContext(ctx)

	lr, _, stateMgr, diags := b.localRun(ctx, op)
	return lr, stateMgr, diags
}

func (b *Local) localRun(ctx context.Context, op *backendrun.Operation) (*backendrun.LocalRun, *configload.Snapshot, statemgr.Full, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics

	// Get the latest state.
	log.Printf("[TRACE] backend/local: requesting state manager for workspace %q", op.Workspace)
	s, sDiags := b.StateMgr(op.Workspace)
	if sDiags.HasErrors() {
		diags = diags.Append(fmt.Errorf("error loading state: %w", sDiags.Err()))
		return nil, nil, nil, diags
	}
	log.Printf("[TRACE] backend/local: requesting state lock for workspace %q", op.Workspace)
	if diags := op.StateLocker.Lock(s, op.Type.String()); diags.HasErrors() {
		return nil, nil, nil, diags
	}

	defer func() {
		// If we're returning with errors, and thus not producing a valid
		// context, we'll want to avoid leaving the workspace locked.
		if diags.HasErrors() {
			diags = diags.Append(op.StateLocker.Unlock())
		}
	}()

	log.Printf("[TRACE] backend/local: reading remote state for workspace %q", op.Workspace)
	if err := s.RefreshState(); err != nil {
		diags = diags.Append(fmt.Errorf("error loading state: %w", err))

View on GitHub (pinned to d32a084675)