{"record":{"id":"ba9bce327ee2e6ab","repo":"hashicorp/terraform","slug":"state-is-already-unlocked","errorCode":null,"errorMessage":"state is already unlocked","messagePattern":"state is already unlocked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/backend/remote-state/kubernetes/client.go","lineNumber":312,"sourceCode":"\t\treturn \"\", err\n\t}\n\n\treturn info.ID, err\n}\n\nfunc (c *RemoteClient) Unlock(id string) error {\n\tleaseName, err := c.createLeaseName()\n\tif err != nil {\n\t\treturn err\n\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 {","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/kubernetes/client.go#L294-L330","documentation":"Returned by the k8s backend Unlock when the Lease's HolderIdentity is nil (client.go:311-313). A nil holder means no one currently holds the lock, so there is nothing to unlock. This is a plain error (not a LockError) because there is no lock info to attach.","triggerScenarios":"Calling Unlock(id) when the state was never locked, or was already unlocked by a prior call, or the Lease was manually cleared. Also after a force-unlock that nilled HolderIdentity.","commonSituations":"Terraform retrying an unlock after a partial failure that already cleared the holder; manual kubectl editing of the Lease; a prior run that unlocked but did not finish cleanly; calling unlock with a stale lock ID after someone else unlocked.","solutions":["Treat as benign if state is expected to be unlocked — verify with kubectl get lease <name> -o yaml.","Ensure Unlock is called exactly once per acquired lock; guard against double-unlock in your code.","If a real lock should exist, investigate what cleared HolderIdentity (manual edit, concurrent run).","Avoid force-unlock unless the Lease truly shows a holder you cannot release."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Check the Lease holder before attempting unlock:\n// lease, _ := getLease(name)\n// if lease.Spec.HolderIdentity == nil { /* already unlocked, skip */ }","typeGuard":null,"tryCatchPattern":"// if err := client.Unlock(id); err != nil {\n//   if strings.Contains(err.Error(), \"state is already unlocked\") { /* benign */ }\n// }","preventionTips":["Call Unlock at most once per Lock; track lock state in your caller.","Before unlocking, kubectl get lease <name> -o yaml to confirm a holder exists.","Avoid manual edits of the Lease HolderIdentity."],"tags":["kubernetes-backend","unlock","lease","locking"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}