hashicorp/terraform · error

Error locking state: %s

Error message

Error locking state: %s

What it means

In Meta.backend_C_r_s (meta_backend.go:1778) Terraform tried to acquire the state lock (clistate.Locker.Lock) before writing the freshly reconfigured single-state backend into the workdir-state file and failed. The wrapped `%s` is the underlying lock error, typically 'state is locked' with the ID and info of whoever holds it, or a transport error reaching the remote backend.

Source

Thrown at internal/command/meta_backend.go:1778

			for _, localState := range localStates {
				// We always delete the local state, unless that was our new state too.
				if err := localState.WriteState(nil); err != nil {
					diags = diags.Append(&errBackendMigrateLocalDelete{err})
					return nil, diags
				}
				if err := localState.PersistState(nil); err != nil {
					diags = diags.Append(&errBackendMigrateLocalDelete{err})
					return nil, diags
				}
			}
		}
	}

	if m.stateLock {
		view := views.NewStateLocker(vt, m.View)
		stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
		if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
			diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
			return nil, diags
		}
		defer stateLocker.Unlock()
	}

	// Store the metadata in our saved state location
	s := sMgr.State()
	if s == nil {
		s = workdir.NewBackendStateFile()
	}
	s.Backend = &workdir.BackendConfigState{
		Type: c.Type,
		Hash: uint64(cHash),
	}
	err := s.Backend.SetConfig(configVal, b.ConfigSchema())
	if err != nil {
		diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
		return nil, diags

View on GitHub (pinned to c9def3e214)

Solutions

  1. Inspect the wrapped error for the lock holder info; wait for that process to finish or coordinate to stop it.
  2. If the lock is genuinely stale (the holder is gone), run `terraform force-unlock <LOCK_ID>` then retry init.
  3. For network/auth errors, verify credentials, endpoint, and connectivity to the backend, then retry.
  4. Use `-lock-timeout=<duration>` to make init wait for the lock instead of failing immediately.

Example fix

// before: terraform init   (fails: Error locking state: state is locked)
// after: terraform force-unlock <LOCK_ID> && terraform init
Defensive patterns

Strategy: retry

Validate before calling

// Before init, confirm no stale lock exists on the backend.
// (Authoritative check is backend-specific; shown as a preflight concept.)
func ensureStateUnlocked(runner func() error, lockTimeout time.Duration) error {
    return retry(lockTimeout, time.Second*2, runner) // wait out a transient holder
}

Try / catch

// On 'Error locking state', distinguish stale vs active and act accordingly.
if err := cmd.Init(); err != nil && strings.Contains(err.Error(), "Error locking state") {
    if id := extractLockID(err.Error()); id != "" && lockLooksStale(err) {
        _ = cmd.ForceUnlock(id) // only after confirming the holder is gone
        return cmd.Init()
    }
}

Prevention

When it happens

Trigger: Another Terraform process (or CI job) currently holds the state lock on the same backend; a previous run crashed without releasing the lock leaving a stale lock object; network/credentials failure contacting the locking backend (S3 DynamoDB, Consul, Azure, GCS, etc.).

Common situations: Concurrent CI pipelines targeting the same state, a developer's `terraform apply` still running in another shell, a killed run leaving a dangling lock, expired/temporary cloud credentials while locking.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/5f7147e6d3f8c938. Report an issue: GitHub.