hashicorp/terraform · error · statemgr.LockError
failed to unlock DynamoDB: %v
Error message
failed to unlock DynamoDB: %v
What it means
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.
Source
Thrown at internal/backend/remote-state/s3/client.go:495
// Double unlocking: DynamoDB + file
log.Info("Attempting to unlock remote state (S3 Native and DynamoDB)...")
ferr := c.unlockWithFile(ctx, id, lockErr, log)
derr := c.unlockWithDynamoDB(ctx, id, lockErr)
if ferr != nil && derr != nil {
lockErr.Err = fmt.Errorf("failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v", ferr, derr)
return lockErr
}
if ferr != nil {
lockErr.Err = fmt.Errorf("failed to unlock S3: %v", ferr)
return lockErr
}
if derr != nil {
lockErr.Err = fmt.Errorf("failed to unlock DynamoDB: %v", derr)
return lockErr
}
log.Info("Unlocked remote state (S3 Native and DynamoDB)")
return nil
}
// unlockWithFile attempts to unlock the remote state by deleting the lock file from Amazon S3.
//
// This method is used when the S3 native locking mechanism is in use, which uses a `.tflock` file
// to manage state locking. The function deletes the lock file to release the lock, allowing other
// Terraform clients to acquire the lock on the same state file.
func (c *RemoteClient) unlockWithFile(ctx context.Context, id string, lockErr *statemgr.LockError, log hclog.Logger) error {
getInput := &s3.GetObjectInput{
Bucket: aws.String(c.bucketName),
Key: aws.String(c.lockFilePath),
}
View on GitHub (pinned to c9def3e214)
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>"}}'.
Example fix
// before: table was deleted
// after: recreate or point backend at the correct table
terraform {
backend "s3" {
dynamodb_table = "terraform-locks" # must exist
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the DynamoDB table exists before running
func ensureLockTable(ctx context.Context, ddbs *dynamodb.Client, table string) error {
if _, err := ddbs.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {
return fmt.Errorf("lock table %q unavailable: %w", table, err)
}
return nil
} Try / catch
err := client.Unlock(id)
if le, ok := err.(*statemgr.LockError); ok && strings.Contains(le.Err.Error(), "failed to unlock DynamoDB") {
// only DDB failed; S3 is clean, fix DDB side then retry
} Prevention
- 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.
When it happens
Trigger: Unlock(id) with useLockFile=true && ddbTable set, where DynamoDB GetItem or DeleteItem fails (ResourceNotFoundException on the table, AccessDenied, ConditionalCheckFailed, throttling) while S3 deletion succeeds.
Common situations: 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.
Related errors
- failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoD
- failed to retrieve lock info for lock ID %q: %s
- failed to unlock S3: %v
- unable to retrieve file from S3 bucket '%s' with key '%s': %
- failed to delete the lock file: %w
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/bfb2cc9fdc189067.
Report an issue: GitHub.