{"record":{"id":"a3200381244e868e","repo":"hashicorp/terraform","slug":"lock-id-q-does-not-match-existing-lock-q","errorCode":null,"errorMessage":"lock ID %q does not match existing lock (%q)","messagePattern":"lock ID %q does not match existing lock \\(%q\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":572,"sourceCode":"\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 {\n\t// TODO: store the path and lock ID in separate fields, and have proper\n\t// projection expression only delete the lock if both match, rather than\n\t// checking the ID from the info field first.\n\tlockInfo, err := c.getLockInfoWithDynamoDB(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve lock info for lock ID %q: %s\", id, err)\n\t}\n\tlockErr.Info = lockInfo\n\n\tif lockInfo.ID != id {\n\t\treturn fmt.Errorf(\"lock ID %q does not match existing lock (%q)\", id, lockInfo.ID)\n\t}\n\n\tparams := &dynamodb.DeleteItemInput{\n\t\tKey: map[string]dynamodbtypes.AttributeValue{\n\t\t\t\"LockID\": &dynamodbtypes.AttributeValueMemberS{\n\t\t\t\tValue: c.lockPath(),\n\t\t\t},\n\t\t},\n\t\tTableName: aws.String(c.ddbTable),\n\t}\n\t_, err = c.dynClient.DeleteItem(ctx, params)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n","sourceCodeStart":554,"sourceCodeEnd":590,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L554-L590","documentation":"Returned by unlockWithDynamoDB after the lock info is read from DynamoDB but the stored lockInfo.ID does not match the id passed to Unlock. This is the DynamoDB equivalent of the S3 ownership check (387) and prevents releasing another client's lock.","triggerScenarios":"Unlock(id) -> unlockWithDynamoDB where lockInfo.ID (from the Info attribute) != id: operator used a stale/wrong force-unlock ID, or another run re-acquired the lock after the current id was captured.","commonSituations":"Stale 'terraform force-unlock' id from an old error message; multiple CI runners racing on the same workspace; manual edits to the DynamoDB Info attribute.","solutions":["Use the most recent lock ID from the current 'Error acquiring the state lock' message.","Query the table to read the live lockInfo.ID: aws dynamodb get-item with ProjectionExpression Info.","Coordinate to avoid concurrent runs against the same workspace.","If the stored lock is definitively orphaned, delete the row manually with the correct LockID key."],"exampleFix":"# before: stale id\nterraform force-unlock old-id\n# after: read current id then unlock\naws dynamodb get-item --table-name locks --key '{\"LockID\":{\"S\":\"<bucket>/<path>\"}}'\nterraform force-unlock <current-id>","handlingStrategy":"validation","validationCode":"// Read the live DynamoDB lock ID and compare before unlock\nfunc liveDynamoLockID(ctx context.Context, ddbs *dynamodb.Client, table, lockPath string) (string, error) {\n  resp, err := ddbs.GetItem(ctx, &dynamodb.GetItemInput{\n    Key: map[string]types.AttributeValue{\"LockID\": &types.AttributeValueMemberS{Value: lockPath}},\n    ProjectionExpression: aws.String(\"Info\"),\n    TableName: &table, ConsistentRead: aws.Bool(true),\n  })\n  if err != nil { return \"\", err }\n  var li statemgr.LockInfo\n  if s, ok := resp.Item[\"Info\"].(*types.AttributeValueMemberS); ok {\n    if err := json.Unmarshal([]byte(s.Value), &li); err != nil { return \"\", err }\n  }\n  return li.ID, nil\n}","typeGuard":null,"tryCatchPattern":"if err := client.Unlock(id); err != nil {\n    if strings.Contains(err.Error(), \"does not match existing lock\") {\n        // re-read live id and re-issue force-unlock\n    }\n}","preventionTips":["Always use the lock ID from the most recent error message.","Serialize CI runs per workspace to avoid ID churn.","Read the live ID before force-unlock.","Do not let external scripts edit the Info attribute."],"tags":["dynamodb","terraform-state","locking","validation"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}