{"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":"validation","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/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L525-L561","documentation":"Returned by unlockWithFile when the lock ID passed to Unlock(id) does not equal the ID field stored in the retrieved .tflock file. This is a deliberate safety check: Terraform only unlocks the lock it owns, preventing one client from forcibly releasing another client's lock.","triggerScenarios":"Unlock(id) is called with an id that differs from lockInfo.ID parsed from the S3 lock file — e.g. operator passes the wrong force-unlock ID, or the lock was re-acquired by another process so the stored ID changed.","commonSituations":"Running 'terraform force-unlock <wrong-id>'; copying a lock ID from a stale log after another teammate re-locked the state; running two CI jobs against the same workspace where the second acquired a new lock.","solutions":["Run 'terraform force-unlock' with the CURRENT lock ID shown in the latest 'Error acquiring the state lock' message, not an old one.","Inspect the .tflock file contents to read the live lockInfo.ID.","Coordinate with teammates/CI to ensure only one client owns the lock.","If the stored lock is genuinely orphaned and you are sure, delete the .tflock file manually after confirming no active run."],"exampleFix":"# before: using a stale lock id\nterraform force-unlock a1b2c3-old\n# after: fetch the current id from the latest lock message\nterraform force-unlock <current-id-from-error>","handlingStrategy":"validation","validationCode":"// Read the live lock ID and compare before calling Unlock\nfunc currentLockID(ctx context.Context, s3c *s3.Client, bucket, lockKey string) (string, error) {\n  out, err := s3c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey})\n  if err != nil { return \"\", err }\n  defer out.Body.Close()\n  data, _ := io.ReadAll(out.Body)\n  var li statemgr.LockInfo\n  if err := json.Unmarshal(data, &li); err != nil { return \"\", err }\n  return li.ID, nil\n}\n\n// usage\nlive, _ := currentLockID(ctx, s3c, bucket, lockKey)\nif live != id { return fmt.Errorf(\"refusing unlock: live id %s != %s\", live, id) }","typeGuard":null,"tryCatchPattern":"if err := client.Unlock(id); err != nil {\n    if strings.Contains(err.Error(), \"does not match the existing lock ID\") {\n        // fetch the real id and re-issue force-unlock\n    }\n}","preventionTips":["Always copy the lock ID from the most recent error message.","Coordinate CI to prevent overlapping runs on one workspace.","Build a helper that reads the live lock ID before issuing force-unlock.","Never reuse a lock ID across separate lock acquisitions."],"tags":["s3","terraform-state","locking","validation"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}