hashicorp/terraform · error
lock id %q does not match existing lock
Error message
lock id %q does not match existing lock
What it means
Unlock() requires the caller's lock id to match the lease's current HolderIdentity. When they differ, a LockError is returned (not a plain error) carrying the existing lock info, so Terraform can surface the real holder and guide a force-unlock decision.
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 c9def3e214)
Solutions
- Re-read the current lock info from the lease (the error's LockError.Info carries it) and use that Lock ID to unlock.
- If the current holder is an abandoned run, run `terraform force-unlock <current-lock-id>` with the correct id.
- Avoid unlocking with cached/old lock IDs; always derive the id from the live lease.
Example fix
# before - unlocking with a stale lock ID captured earlier terraform force-unlock 2024-01-01-old-run-id # after - use the lock ID from the live lease shown in the error terraform force-unlock 2024-08-07-current-run-id
Defensive patterns
Strategy: validation
Validate before calling
// Always read the current holder from the lease before unlocking
lease, err := c.getLease(name)
if err != nil { return err }
if lease.Spec.HolderIdentity == nil { return nil }
current := *lease.Spec.HolderIdentity
if current != id {
// use `current` for force-unlock instead of the stale `id`
}
return c.Unlock(current) Type guard
func lockIDMatches(lease *coordinationv1.Lease, id string) bool {
return lease != nil &&
lease.Spec.HolderIdentity != nil &&
*lease.Spec.HolderIdentity == id
} Prevention
- Derive the lock ID from the live lease, never from a cached value.
- Before force-unlock, confirm no active run currently holds the lock.
- Communicate lock IDs clearly within teams to avoid cross-run unlocks.
When it happens
Trigger: Calling Unlock(id) at client.go:320-324 where *lease.Spec.HolderIdentity != id - the id passed (typically from an old run's lock info) does not equal the lease's current holder.
Common situations: Trying to unlock with a stale lock ID from a previous run after the lock was re-acquired by a newer run; a teammate force-unlocked and re-locked under a new id; an expired lease that was re-acquired; copy-pasting the wrong lock ID into force-unlock.
Related errors
- %v Additionally, unlocking the state in Kubernetes faile
- state is already unlocked
- failed to lock inmem state: %s
- Failed to configure: %s
- secret_suffix must not end with '-<number>', got %q
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/344e31d086dda324.
Report an issue: GitHub.