{"record":{"id":"344e31d086dda324","repo":"hashicorp/terraform","slug":"lock-id-q-does-not-match-existing-lock-344e31","errorCode":null,"errorMessage":"lock id %q does not match existing lock","messagePattern":"lock id %q does not match existing lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/kubernetes/client.go","lineNumber":322,"sourceCode":"\t}\n\n\tlease, err := c.getLease(leaseName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif lease.Spec.HolderIdentity == nil {\n\t\treturn fmt.Errorf(\"state is already unlocked\")\n\t}\n\n\tlockInfo, err := c.getLockInfo(lease)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlockErr := &statemgr.LockError{Info: lockInfo}\n\tif *lease.Spec.HolderIdentity != id {\n\t\tlockErr.Err = fmt.Errorf(\"lock id %q does not match existing lock\", id)\n\t\treturn lockErr\n\t}\n\n\tlease.Spec.HolderIdentity = nil\n\tremoveLockInfo(lease)\n\n\t_, err = c.kubernetesLeaseClient.Update(context.Background(), lease, metav1.UpdateOptions{})\n\tif err != nil {\n\t\tlockErr.Err = err\n\t\treturn lockErr\n\t}\n\n\treturn nil\n}\n\nfunc (c *RemoteClient) getLockInfo(lease *coordinationv1.Lease) (*statemgr.LockInfo, error) {\n\tlockData, ok := getLockInfo(lease)\n\tif len(lockData) == 0 || !ok {","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/kubernetes/client.go#L304-L340","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before - unlocking with a stale lock ID captured earlier\nterraform force-unlock 2024-01-01-old-run-id\n\n# after - use the lock ID from the live lease shown in the error\nterraform force-unlock 2024-08-07-current-run-id","handlingStrategy":"validation","validationCode":"// Always read the current holder from the lease before unlocking\nlease, err := c.getLease(name)\nif err != nil { return err }\nif lease.Spec.HolderIdentity == nil { return nil }\ncurrent := *lease.Spec.HolderIdentity\nif current != id {\n    // use `current` for force-unlock instead of the stale `id`\n}\nreturn c.Unlock(current)","typeGuard":"func lockIDMatches(lease *coordinationv1.Lease, id string) bool {\n    return lease != nil &&\n        lease.Spec.HolderIdentity != nil &&\n        *lease.Spec.HolderIdentity == id\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["kubernetes","remote-state","locking","terraform-backend","force-unlock"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}