{"record":{"id":"bf308b322a5b526d","repo":"hashicorp/terraform","slug":"lock-id-does-not-match-existing-lock-bf308b","errorCode":null,"errorMessage":"lock ID does not match existing lock","messagePattern":"lock ID does not match existing lock","errorType":"validation","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/cloud/state.go","lineNumber":475,"sourceCode":"\t\treturn nil\n\t}\n\n\tctx := context.Background()\n\n\t// We first check if there was an error while uploading the latest\n\t// state. If so, we will not unlock the workspace to prevent any\n\t// changes from being applied until the correct state is uploaded.\n\tif s.stateUploadErr {\n\t\treturn nil\n\t}\n\n\tlockErr := &statemgr.LockError{Info: s.lockInfo}\n\n\t// With lock info this should be treated as a normal unlock.\n\tif s.lockInfo != nil {\n\t\t// Verify the expected lock ID.\n\t\tif s.lockInfo.ID != id {\n\t\t\tlockErr.Err = fmt.Errorf(\"lock ID does not match existing lock\")\n\t\t\treturn lockErr\n\t\t}\n\n\t\t// Unlock the workspace.\n\t\terr := RetryBackoff(ctx, func() error {\n\t\t\t_, err := s.tfeClient.Workspaces.Unlock(ctx, s.workspace.ID)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, tfe.ErrWorkspaceLockedStateVersionStillPending) {\n\t\t\t\t\t// This is a retryable error.\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t// This will not be retried\n\t\t\t\treturn &errorUnlockFailed{innerError: err}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\n\t\tif err != nil {","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/cloud/state.go#L457-L493","documentation":"In Unlock's normal path (when s.lockInfo is set), the provided id is compared against the internally recorded s.lockInfo.ID. If they differ, unlock is refused so a caller cannot release a lock it does not own.","triggerScenarios":"Unlock is called with a lock ID that was not the one returned by the matching Lock call; e.g. two state managers sharing state incorrectly, or Unlock called twice with different IDs.","commonSituations":"Programmatic misuse of the statemgr.Full interface, or stale lock info after a restart trying to unlock with the wrong handle.","solutions":["Pass the exact lock ID returned by the corresponding Lock call.","If the real lock ID is unknown, use the force-unlock path with the \"<org>/<workspace>\" ID.","Ensure only the process that acquired the lock releases it."],"exampleFix":"// before: unlocking with the wrong id\nstate.Unlock(someOtherID)\n\n// after: unlock with the id returned by Lock\nlockID, _ := state.Lock(info)\nstate.Unlock(lockID)","handlingStrategy":"validation","validationCode":"// Validate the lock ID matches the one we hold before unlocking\nif s.lockInfo != nil && s.lockInfo.ID != providedID {\n    return fmt.Errorf(\"refusing unlock: expected %q, got %q\", s.lockInfo.ID, providedID)\n}","typeGuard":null,"tryCatchPattern":"if err := state.Unlock(lockID); err != nil {\n    var lockErr *statemgr.LockError\n    if errors.As(err, &lockErr) && strings.Contains(lockErr.Err.Error(), \"does not match\") {\n        // use the correct ID from Lock(), or the force-unlock path\n    }\n}","preventionTips":["Always unlock with the exact ID returned by Lock()","Have only the locking process release the lock","Use the force-unlock path only with org/workspace ID"],"tags":["locking","unlock","programming-error","misuse"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}