{"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":"validation","errorClass":"LockError","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/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/kubernetes/client.go#L304-L340","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use the lock ID returned by the Lock call that acquired THIS lock — do not reuse IDs across runs.","Inspect the attached LockError.Info to see who actually holds the lock and coordinate with them.","If the holder is stale/orphaned, force-unlock using the correct existing lock ID (from LockError.Info.ID).","Serialize access to the workspace so only one run holds the lock at a time."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Verify the lock ID matches the current holder before unlocking:\n// lease, _ := getLease(name)\n// if lease.Spec.HolderIdentity != nil && *lease.Spec.HolderIdentity != id { /* mismatch */ }","typeGuard":null,"tryCatchPattern":"// if err := client.Unlock(id); err != nil {\n//   var le *statemgr.LockError\n//   if errors.As(err, &le) && le.Info != nil {\n//     actualHolder := le.Info.ID // coordinate with this owner\n//   }\n// }","preventionTips":["Always use the lock ID returned by the Lock call that acquired the lock for this run.","Never hard-code or cache lock IDs across runs.","Inspect LockError.Info to identify the real holder before force-unlocking."],"tags":["kubernetes-backend","unlock","lock-id","locking","lock-error"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}