{"record":{"id":"3b31ec431b74d016","repo":"hashicorp/terraform","slug":"lock-id-does-not-match-existing-lock","errorCode":null,"errorMessage":"lock ID does not match existing lock","messagePattern":"lock ID does not match existing lock","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote/backend_state.go","lineNumber":220,"sourceCode":"\n// Unlock the remote state.\nfunc (r *remoteClient) Unlock(id string) error {\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 r.stateUploadErr {\n\t\treturn nil\n\t}\n\n\tlockErr := &statemgr.LockError{Info: r.lockInfo}\n\n\t// With lock info this should be treated as a normal unlock.\n\tif r.lockInfo != nil {\n\t\t// Verify the expected lock ID.\n\t\tif r.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\t// Unlock the workspace.\n\t\terr := RetryBackoff(ctx, func() error {\n\t\t\t_, err := r.client.Workspaces.Unlock(ctx, r.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","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote/backend_state.go#L202-L238","documentation":"On Unlock with a populated r.lockInfo, the supplied id is compared against the locally-tracked r.lockInfo.ID. A mismatch means the caller is trying to unlock with a different ID than the one returned by Lock — refused to avoid releasing someone else's lock. Returned inside a statemgr.LockError.","triggerScenarios":"r.lockInfo.ID != id: the process stored a lock ID, then Unlock was called with a different ID (e.g. copied from another workspace, or a stale id from a prior run).","commonSituations":"Manually invoking Unlock with a mismatched id; state persisted across processes but lockInfo not; concurrent attempts where one already updated lockInfo.","solutions":["Pass the exact lock ID returned by the original Lock call (don't hand-edit it).","If the original ID is lost, use the force-unlock path with the org/workspace string instead.","Avoid concurrent unlock attempts on the same remoteClient instance."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Validate id matches the tracked lock before calling Unlock\nif r.lockInfo != nil && r.lockInfo.ID != id {\n    return fmt.Errorf(\"refusing unlock: expected %s, got %s\", r.lockInfo.ID, id)\n}","typeGuard":"func lockIdMatches(info *statemgr.LockInfo, id string) bool {\n    return info != nil && info.ID == id\n}","tryCatchPattern":null,"preventionTips":["Always pass the exact ID returned by Lock.","Don't share/persist a remoteClient across unrelated lock cycles.","Use force-unlock with the org/workspace string when the original ID is lost."],"tags":["terraform","remote-backend","tfe","locking","unlock","state"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}