hashicorp/terraform · error
failed to unlock both S3 and DynamoDB: S3 error
Error message
failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v
What it means
During dual-lock unlock (both S3 file and DynamoDB enabled), Terraform attempts both release paths independently. This error reports that BOTH the S3 file unlock and the DynamoDB unlock failed, leaving the state fully locked. The two underlying errors are embedded with %v (not %w), so the original lockErr context carries the failure.
Solutions
- Refresh AWS credentials (`aws sts get-caller-identity` must succeed) and retry `terraform force-unlock <id>`; transient dual failures often clear on retry.
- Manually remove both locks: delete the S3 `.tflock` object AND the DynamoDB LockID row, using the lock ID from the error.
- Check the AWS status page for an active S3/DynamoDB incident in the configured region before retrying.
- Verify the IAM principal retains s3:GetObject, s3:DeleteObject, and dynamodb:DeleteItem on the relevant resources.
- Inspect both underlying %v messages to determine if one failure is benign (e.g. NoSuchKey on an already-deleted lock file) so you can target the real blockage.
Example fix
# manually clear both locks after a dual unlock failure
aws s3api delete-object --bucket tf-state-prod --key prod/terraform.tflock.tflock
aws dynamodb delete-item \
--table-name terraform-locks \
--key '{"LockID":{"S":"tf-state-prod/prod/terraform.tfstate"}}' Defensive patterns
Strategy: retry
Validate before calling
// Confirm both backends are reachable before attempting dual unlock.
func canDualUnlock(ctx context.Context, s3c *s3.Client, ddb *dynamodb.Client, bucket, table string) error {
if _, err := s3c.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}); err != nil {
return err
}
if _, err := ddb.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {
return err
}
return nil
} Try / catch
// After a dual-unlock failure, instruct the operator with both errors and the lock ID.
if ferr != nil && derr != nil {
lockErr.Err = fmt.Errorf("failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v; "+
"run `terraform force-unlock %s` once access is restored", ferr, derr, id)
return lockErr
} Prevention
- Use long-lived-enough STS sessions for interactive applies so credentials do not expire mid-unlock.
- Monitor AWS health dashboards for S3/DynamoDB incidents in the state region.
- Run `terraform force-unlock <id>` promptly after a crash to avoid leaving dual locks stranded.
- Keep the IAM policy complete (Get+Delete on S3 lock key, Get+Delete on DDB row) at all times.
When it happens
Trigger: Both unlockWithFile (client.go:481) and unlockWithDynamoDB (client.go:482) return non-nil errors. Triggers: simultaneous loss of S3 and DynamoDB access (credentials revoked mid-run, region outage), permissions revoked for both services, or the lock file and DDB item were both already deleted by a concurrent force-unlock causing both lookups to fail.
Common situations: AWS regional incident affecting both S3 and DynamoDB, an expired STS session that breaks all AWS calls during a long apply, or two operators force-unlocking concurrently so each unlock path finds the target already gone.
Related errors
- failed to clean up file lock after DynamoDB lock error
- failed to delete the lock file
- failed to read the body of the S3 object
- failed to retrieve lock info for lock ID
- failed to unlock DynamoDB
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/1df679d7381be333.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/s3/client.go:485
if !c.useLockFile && c.ddbTable != "" {
log.Info("Attempting to unlock remote state (DynamoDB only)...")
if err := c.unlockWithDynamoDB(ctx, id, lockErr); err != nil {
lockErr.Err = err
return lockErr
}
log.Info("Unlocked remote state (DynamoDB only)")
return nil
}
// 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.View on GitHub (pinned to d32a084675)