{"record":{"id":"bfb2cc9fdc189067","repo":"hashicorp/terraform","slug":"failed-to-unlock-dynamodb-v","errorCode":null,"errorMessage":"failed to unlock DynamoDB: %v","messagePattern":"failed to unlock DynamoDB: (.+?)","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":495,"sourceCode":"\n\t// Double unlocking: DynamoDB + file\n\tlog.Info(\"Attempting to unlock remote state (S3 Native and DynamoDB)...\")\n\n\tferr := c.unlockWithFile(ctx, id, lockErr, log)\n\tderr := c.unlockWithDynamoDB(ctx, id, lockErr)\n\n\tif ferr != nil && derr != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v\", ferr, derr)\n\t\treturn lockErr\n\t}\n\n\tif ferr != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to unlock S3: %v\", ferr)\n\t\treturn lockErr\n\t}\n\n\tif derr != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to unlock DynamoDB: %v\", derr)\n\t\treturn lockErr\n\t}\n\n\tlog.Info(\"Unlocked remote state (S3 Native and DynamoDB)\")\n\treturn nil\n}\n\n// unlockWithFile attempts to unlock the remote state by deleting the lock file from Amazon S3.\n//\n// This method is used when the S3 native locking mechanism is in use, which uses a `.tflock` file\n// to manage state locking. The function deletes the lock file to release the lock, allowing other\n// Terraform clients to acquire the lock on the same state file.\nfunc (c *RemoteClient) unlockWithFile(ctx context.Context, id string, lockErr *statemgr.LockError, log hclog.Logger) error {\n\tgetInput := &s3.GetObjectInput{\n\t\tBucket: aws.String(c.bucketName),\n\t\tKey:    aws.String(c.lockFilePath),\n\t}\n","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L477-L513","documentation":"Returned by RemoteClient.Unlock in dual-locking mode when the DynamoDB lock-item deletion failed but the S3 file delete succeeded. It wraps the error from unlockWithDynamoDB, which can originate from getLockInfoWithDynamoDB (GetItem) or the final DeleteItem call. The S3 lock is gone, so only the DynamoDB row remains.","triggerScenarios":"Unlock(id) with useLockFile=true && ddbTable set, where DynamoDB GetItem or DeleteItem fails (ResourceNotFoundException on the table, AccessDenied, ConditionalCheckFailed, throttling) while S3 deletion succeeds.","commonSituations":"The DynamoDB lock table was renamed/deleted after lock acquisition; the IAM role lost dynamodb:DeleteItem; provisioned-capacity throttling under heavy concurrency; or the table is in a different region/account than the S3 bucket.","solutions":["Parse the wrapped DynamoDB error: ResourceNotFoundException means the table is gone, ProvisionedThroughputExceededException needs capacity scaling, AccessDenied needs an IAM fix.","Confirm the configured dynamodb_table still exists and matches the one holding the LockID item.","Grant dynamodb:GetItem + dynamodb:DeleteItem on the table ARN to the running principal.","Delete the stale row directly: aws dynamodb delete-item --table-name <tbl> --key '{\"LockID\":{\"S\":\"<bucket>/<path>\"}}'."],"exampleFix":"// before: table was deleted\n// after: recreate or point backend at the correct table\nterraform {\n  backend \"s3\" {\n    dynamodb_table = \"terraform-locks\" # must exist\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Confirm the DynamoDB table exists before running\nfunc ensureLockTable(ctx context.Context, ddbs *dynamodb.Client, table string) error {\n  if _, err := ddbs.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {\n    return fmt.Errorf(\"lock table %q unavailable: %w\", table, err)\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":"err := client.Unlock(id)\nif le, ok := err.(*statemgr.LockError); ok && strings.Contains(le.Err.Error(), \"failed to unlock DynamoDB\") {\n    // only DDB failed; S3 is clean, fix DDB side then retry\n}","preventionTips":["Pin the dynamodb_table name in code review so it is not silently changed.","Use on-demand billing or size read/write units to the concurrency of your CI.","Keep DynamoDB in the same region as the S3 state bucket unless explicitly cross-region.","Grant dynamodb:GetItem + DeleteItem on the lock table ARN."],"tags":["dynamodb","terraform-state","locking","iam","aws"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}