{"record":{"id":"cec5d8fab78d21b8","repo":"hashicorp/terraform","slug":"lock-id-q-does-not-match-existing-lock-cec5d8","errorCode":null,"errorMessage":"lock id %q does not match existing lock","messagePattern":"lock id %q does not match existing lock","errorType":"validation","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oss/client.go","lineNumber":379,"sourceCode":"\t}\n\treturn lockInfo, nil\n}\nfunc (c *RemoteClient) Unlock(id string) error {\n\tif c.otsTable == \"\" {\n\t\treturn nil\n\t}\n\n\tlockErr := &statemgr.LockError{}\n\n\tlockInfo, err := c.getLockInfo()\n\tif err != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to retrieve lock info: %s\", err)\n\t\treturn lockErr\n\t}\n\tlockErr.Info = lockInfo\n\n\tif lockInfo.ID != id {\n\t\tlockErr.Err = fmt.Errorf(\"lock id %q does not match existing lock\", id)\n\t\treturn lockErr\n\t}\n\tparams := &tablestore.DeleteRowRequest{\n\t\tDeleteRowChange: &tablestore.DeleteRowChange{\n\t\t\tTableName: c.otsTable,\n\t\t\tPrimaryKey: &tablestore.PrimaryKey{\n\t\t\t\tPrimaryKeys: []*tablestore.PrimaryKeyColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tColumnName: pkName,\n\t\t\t\t\t\tValue:      c.lockPath(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tCondition: &tablestore.RowCondition{\n\t\t\t\tRowExistenceExpectation: tablestore.RowExistenceExpectation_IGNORE,\n\t\t\t},\n\t\t},\n\t}","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/oss/client.go#L361-L397","documentation":"In RemoteClient.Unlock() (client.go:378-379), the lock row was read successfully but lockInfo.ID != id - the caller-supplied lock ID does not match the stored one. Unlock refuses to delete someone else's lock. This is a deliberate safety check returned as a statemgr.LockError.","triggerScenarios":"Unlock(id) where id differs from the Info.ID in the OTS lock row. Happens with an outdated/stale lock ID, a workspace re-locked by another process, or a hand-copied wrong ID.","commonSituations":"User force-unlocks with an old lock ID from a prior run; another CI job re-locked the workspace; lock ID truncated/typo'd when passed to terraform force-unlock.","solutions":["Re-read the current lock info (terraform force-unlock prints the held ID, or check the OTS Info column) and use that ID.","Confirm no other process re-acquired the lock after you recorded the ID.","If certain the held lock is stale and yours, use force-unlock with -force if available, or delete the OTS row manually."],"exampleFix":"# before: wrong id\nterraform force-unlock 11111111-...\n# error: lock id \"11111111-...\" does not match existing lock\n\n# after: use the actual held id (from terraform's lock message or OTS row)\nterraform force-unlock 22222222-...","handlingStrategy":"validation","validationCode":"// Before Unlock, fetch the held lock ID and compare; refuse to proceed if mismatched.\nfunc unlockSafe(heldID, suppliedID string) error {\n    if heldID != suppliedID {\n        return fmt.Errorf(\"supplied %q != held %q; not your lock\", suppliedID, heldID)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// On mismatch, re-read the current held ID and retry force-unlock with the right one.\nif strings.Contains(err.Error(), \"does not match existing lock\") {\n    id := readCurrentLockID()\n    return terraformForceUnlock(id)\n}","preventionTips":["Always copy the full lock ID verbatim from terraform's own message.","Re-check the held lock ID before force-unlock if time has passed.","Avoid running concurrent force-unlocks that could re-lock under your nose."],"tags":["alibaba-cloud","tablestore","ots","remote-state","locking","unlock","id-mismatch","go"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}