hashicorp/terraform · error

state already locked

Error message

state %q already locked

What it means

Thrown by LocalState.Lock when a lock is requested but s.lockID is already non-empty — i.e. this LocalState instance already holds a lock on the state file. Terraform local backend state locking is in-process (mu mutex) plus on-disk (.tflock file); this specific error is the in-process re-entrancy guard, not the on-disk contention path (that goes through s.lock()).

Solutions

  1. Ensure Lock is paired with Unlock; track lock ownership in the caller.
  2. If unsure of current state, check the returned lock ID and call Unlock with it before re-locking.
  3. For concurrent operations, serialize access at the orchestrator level rather than relying on double-Lock to no-op.
  4. If a previous run crashed leaving the .tflock file on disk, run `terraform force-unlock <id>` to clear stale on-disk locks (separate from this in-process guard).
Defensive patterns

Strategy: validation

Validate before calling

func (s *LocalState) lockHeld() bool { return s.lockID != "" }

Prevention

When it happens

Trigger: Calling Lock twice on the same LocalState instance without Unlock in between; a code path that retries Lock after a partial success; programmatic embedding that double-locks.

Common situations: Custom automation that calls terraform Lock explicitly twice; a wrapper that re-invokes an operation before the previous Unlock completed; a bug in a higher-level command flow that forgets it already locked.

Related errors


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

Appendix: source

Thrown at internal/command/clistate/local_state.go:188

	s.state = state
	s.readState = s.state.DeepCopy()
	return nil
}

// Lock implements a local filesystem state.Locker.
func (s *LocalState) Lock(info *statemgr.LockInfo) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.stateFileOut == nil {
		if err := s.createStateFiles(); err != nil {
			return "", err
		}
	}

	if s.lockID != "" {
		return "", fmt.Errorf("state %q already locked", s.stateFileOut.Name())
	}

	if err := s.lock(); err != nil {
		info, infoErr := s.lockInfo()
		if infoErr != nil {
			err = errors.Join(err, infoErr)
		}

		lockErr := &statemgr.LockError{
			Info: info,
			Err:  err,
		}

		return "", lockErr
	}

	s.lockID = info.ID
	return s.lockID, s.writeLockInfo(info)

View on GitHub (pinned to d32a084675)