hashicorp/terraform · error · LockError

lock id does not match existing lock

Error message

lock id %q does not match existing lock

What it means

Returned as a statemgr.LockError when Unlock(id) is called with an id that does not match lease.Spec.HolderIdentity (client.go:320-323). The %q is the supplied id. The lock info is attached so callers can report who actually holds the lock. This protects against one run releasing another's lock.

Solutions

  1. Use the lock ID returned by the Lock call that acquired THIS lock — do not reuse IDs across runs.
  2. Inspect the attached LockError.Info to see who actually holds the lock and coordinate with them.
  3. If the holder is stale/orphaned, force-unlock using the correct existing lock ID (from LockError.Info.ID).
  4. Serialize access to the workspace so only one run holds the lock at a time.
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the lock ID matches the current holder before unlocking:
// lease, _ := getLease(name)
// if lease.Spec.HolderIdentity != nil && *lease.Spec.HolderIdentity != id { /* mismatch */ }

Try / catch

// if err := client.Unlock(id); err != nil {
//   var le *statemgr.LockError
//   if errors.As(err, &le) && le.Info != nil {
//     actualHolder := le.Info.ID // coordinate with this owner
//   }
// }

Prevention

When it happens

Trigger: Passing a stale or foreign lock ID to Unlock; a lock ID from a previous run persisted and reused; concurrent runs where one tries to unlock the other's lease.

Common situations: An old lock ID cached in state/scripts; a CI job reusing a lock ID from a prior failed run; manual force-unlock with the wrong ID; two pipelines targeting the same workspace.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/kubernetes/client.go:322

	}

	lease, err := c.getLease(leaseName)
	if err != nil {
		return err
	}

	if lease.Spec.HolderIdentity == nil {
		return fmt.Errorf("state is already unlocked")
	}

	lockInfo, err := c.getLockInfo(lease)
	if err != nil {
		return err
	}

	lockErr := &statemgr.LockError{Info: lockInfo}
	if *lease.Spec.HolderIdentity != id {
		lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
		return lockErr
	}

	lease.Spec.HolderIdentity = nil
	removeLockInfo(lease)

	_, err = c.kubernetesLeaseClient.Update(context.Background(), lease, metav1.UpdateOptions{})
	if err != nil {
		lockErr.Err = err
		return lockErr
	}

	return nil
}

func (c *RemoteClient) getLockInfo(lease *coordinationv1.Lease) (*statemgr.LockInfo, error) {
	lockData, ok := getLockInfo(lease)
	if len(lockData) == 0 || !ok {

View on GitHub (pinned to d32a084675)