{"record":{"id":"810af0e432a5ac46","repo":"hashicorp/terraform","slug":"lock-id-s-does-not-match-the-existing-lock-id-810af0","errorCode":null,"errorMessage":"lock ID '%s' does not match the existing lock ID '%s'","messagePattern":"lock ID '(.+?)' does not match the existing lock ID '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":543,"sourceCode":"\t\tif cerr := getOutput.Body.Close(); cerr != nil {\n\t\t\tlog.Warn(fmt.Sprintf(\"failed to close S3 object body: %v\", cerr))\n\t\t}\n\t}()\n\n\tdata, err := io.ReadAll(getOutput.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read the body of the S3 object: %w\", err)\n\t}\n\n\tlockInfo := &statemgr.LockInfo{}\n\tif err := json.Unmarshal(data, lockInfo); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal JSON data into LockInfo struct: %w\", err)\n\t}\n\tlockErr.Info = lockInfo\n\n\t// Verify that the provided lock ID matches the lock ID of the retrieved lock file.\n\tif lockInfo.ID != id {\n\t\treturn fmt.Errorf(\"lock ID '%s' does not match the existing lock ID '%s'\", id, lockInfo.ID)\n\t}\n\n\t// Delete the lock file to release the lock.\n\t_, err = c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{\n\t\tBucket: aws.String(c.bucketName),\n\t\tKey:    aws.String(c.lockFilePath),\n\t})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete the lock file: %w\", err)\n\t}\n\n\tlog.Debug(fmt.Sprintf(\"Deleted lock file: '%q'\", c.lockFilePath))\n\n\treturn nil\n}\n\nfunc (c *RemoteClient) unlockWithDynamoDB(ctx context.Context, id string, lockErr *statemgr.LockError) error {","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/client.go#L525-L561","documentation":"Thrown inside unlockWithFile when the lock file was read and parsed successfully, but the ID field in the file does not equal the id argument passed to Unlock. This is a safety guard: Terraform refuses to delete a lock it does not own, preventing one operator from silently releasing another's lock.","triggerScenarios":"lockInfo.ID != id at client.go:542. Triggers: force-unlock invoked with the wrong ID, two concurrent runs where each holds a different lock ID, a stale lock ID from a previous (already-cleared) run, or the lock file was overwritten by a newer lock holder.","commonSituations":"Copy-pasting an old lock ID into force-unlock, a teammate acquired a fresh lock after yours timed out, running force-unlock against the wrong workspace's lock file, or the lock file was recreated by another run between when you read the ID and when you unlocked.","solutions":["Re-read the current lock ID from the error's 'existing lock ID' value and force-unlock with THAT id: `terraform force-unlock <existing-id>`.","If you intentionally want to break someone else's lock, confirm with the listed lock holder first, then force-unlock with the existing ID shown in the message.","Double-check the workspace: ensure you are operating on the same workspace/state path that owns the lock.","If the existing ID is unknown, fetch the lock file to read it: `aws s3api get-object ... | jq .ID`.","Avoid running multiple applies concurrently against the same state to prevent ID churn."],"exampleFix":"# before: force-unlock with a stale/wrong ID\nterraform force-unlock aaaaaaaa-1111-2222-3333-444444444444\n# after: use the ID reported in the error's 'existing lock ID'\nterraform force-unlock bbbbbbbb-9999-8888-7777-666666666666","handlingStrategy":"validation","validationCode":"// Before unlock, read the current lock ID from the file and compare.\nfunc currentLockID(ctx context.Context, c *s3.Client, bucket, lockKey string) (string, error) {\n  out, err := c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey})\n  if err != nil { return \"\", err }\n  defer out.Body.Close()\n  b, err := io.ReadAll(out.Body); if err != nil { return \"\", err }\n  var li statemgr.LockInfo\n  if err := json.Unmarshal(b, &li); err != nil { return \"\", err }\n  return li.ID, nil\n}\n// compare to the id you intend to unlock with; abort if mismatch.","typeGuard":"func isLockOwner(fileID, unlockID string) bool { return fileID != \"\" && fileID == unlockID }","tryCatchPattern":"// On mismatch, return the existing ID so the operator can force-unlock correctly.\nif lockInfo.ID != id {\n  return fmt.Errorf(\"lock ID '%s' does not match the existing lock ID '%s'; \"+\n    \"run `terraform force-unlock %s` with the existing ID\", id, lockInfo.ID, lockInfo.ID)\n}","preventionTips":["Always force-unlock with the ID reported by Terraform, not a previously cached one.","Do not run concurrent applies against the same workspace.","Confirm the workspace matches the locked state path before unlocking.","If unsure who owns the lock, fetch the lock file and read its ID/Operation/Who fields."],"tags":["locking","s3","remote-state","lock-id","safety-guard","concurrency","unlock"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}