hashicorp/terraform · error

invalid lock id: . current id

Error message

invalid lock id: %q. current id: %q

What it means

Thrown by LocalState.Unlock when the caller-supplied lock ID does not match the ID currently held by this LocalState (s.lockID). Terraform uses the ID to ensure only the lock owner can release it — preventing one operation from unlocking another's lock. On mismatch the lock is left intact and the current holder's ID is reported so the operator can reconcile.

Solutions

  1. Use the exact lock ID returned by the Lock call that succeeded — do not infer or hard-code.
  2. If the lock is genuinely stale from a dead process, run `terraform force-unlock <id>` with the reported current id.
  3. Serialize concurrent runs against the same local state so IDs do not collide.
  4. Log the lock ID at acquisition so the matching Unlock can be verified.
Defensive patterns

Strategy: validation

Validate before calling

if id != heldID {
    return fmt.Errorf("refusing to unlock: provided %q != held %q", id, heldID)
}

Prevention

When it happens

Trigger: Passing the wrong ID to Unlock (stale, from a different run, or a different state); concurrent processes each holding their own lock and confusing IDs; an orchestrator that generated/forwarded the wrong ID.

Common situations: Two Terraform processes targeting the same local state; CI retried a job and the second attempt tries to unlock with the first attempt's ID; a wrapper recorded the ID incorrectly.

Related errors


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

Appendix: source

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

		}

		return "", lockErr
	}

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

func (s *LocalState) Unlock(id string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.lockID == "" {
		return fmt.Errorf("LocalState not locked")
	}

	if id != s.lockID {
		idErr := fmt.Errorf("invalid lock id: %q. current id: %q", id, s.lockID)
		info, err := s.lockInfo()
		if err != nil {
			idErr = errors.Join(idErr, err)
		}

		return &statemgr.LockError{
			Err:  idErr,
			Info: info,
		}
	}

	os.Remove(s.lockInfoPath())

	fileName := s.stateFileOut.Name()

	unlockErr := s.unlock()

	s.stateFileOut.Close()

View on GitHub (pinned to d32a084675)