hashicorp/terraform · error
lock ID '%s' does not match the existing lock ID '%s'
Error message
lock ID '%s' does not match the existing lock ID '%s'
What it means
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.
Source
Thrown at internal/backend/remote-state/s3/client.go:543
if cerr := getOutput.Body.Close(); cerr != nil {
log.Warn(fmt.Sprintf("failed to close S3 object body: %v", cerr))
}
}()
data, err := io.ReadAll(getOutput.Body)
if err != nil {
return fmt.Errorf("failed to read the body of the S3 object: %w", err)
}
lockInfo := &statemgr.LockInfo{}
if err := json.Unmarshal(data, lockInfo); err != nil {
return fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
}
lockErr.Info = lockInfo
// Verify that the provided lock ID matches the lock ID of the retrieved lock file.
if lockInfo.ID != id {
return fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID)
}
// Delete the lock file to release the lock.
_, err = c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: aws.String(c.bucketName),
Key: aws.String(c.lockFilePath),
})
if err != nil {
return fmt.Errorf("failed to delete the lock file: %w", err)
}
log.Debug(fmt.Sprintf("Deleted lock file: '%q'", c.lockFilePath))
return nil
}
func (c *RemoteClient) unlockWithDynamoDB(ctx context.Context, id string, lockErr *statemgr.LockError) error {View on GitHub (pinned to c9def3e214)
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.
Example fix
# before: using a stale lock id terraform force-unlock a1b2c3-old # after: fetch the current id from the latest lock message terraform force-unlock <current-id-from-error>
Defensive patterns
Strategy: validation
Validate before calling
// Read the live lock ID and compare before calling Unlock
func currentLockID(ctx context.Context, s3c *s3.Client, bucket, lockKey string) (string, error) {
out, err := s3c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey})
if err != nil { return "", err }
defer out.Body.Close()
data, _ := io.ReadAll(out.Body)
var li statemgr.LockInfo
if err := json.Unmarshal(data, &li); err != nil { return "", err }
return li.ID, nil
}
// usage
live, _ := currentLockID(ctx, s3c, bucket, lockKey)
if live != id { return fmt.Errorf("refusing unlock: live id %s != %s", live, id) } Try / catch
if err := client.Unlock(id); err != nil {
if strings.Contains(err.Error(), "does not match the existing lock ID") {
// fetch the real id and re-issue force-unlock
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoD
- failed to unlock S3: %v
- unable to retrieve file from S3 bucket '%s' with key '%s': %
- failed to read the body of the S3 object: %w
- failed to unmarshal JSON data into LockInfo struct: %w
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/810af0e432a5ac46.
Report an issue: GitHub.