hashicorp/terraform · error · statemgr.LockError
failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoD
Error message
failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v
What it means
Thrown by RemoteClient.Unlock when both S3 lock-file deletion (unlockWithFile) AND DynamoDB lock-item deletion (unlockWithDynamoDB) fail simultaneously while running in the dual-locking mode (useLockFile=true and ddbTable set). The wrapped message concatenates the two underlying AWS errors so the operator can see why each backend rejected the delete. Because the lock is held by two independent stores, a partial infrastructure outage or permission scope can cause both to fail at once.
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 c9def3e214)
Solutions
- Inspect the wrapped 'S3 error' and 'DynamoDB error' substrings to identify which AWS API failed for each store and address the more actionable one first.
- Verify the running credentials (aws sts get-caller-identity) and confirm they have both s3:GetObject+DeleteObject on the lock key and dynamodb:GetItem+DeleteItem on the table.
- Re-run the operation once the IAM/credential issue is fixed; the lock file and item still exist so Unlock can retry.
- If the lock is stuck and credentials are correct, manually delete the S3 <lockFilePath> object and the DynamoDB row with LockID = <bucket>/<path> using aws-cli.
Example fix
// before: principal lacks dynamodb:DeleteItem
// after: IAM policy includes
{
"Effect": "Allow",
"Action": ["dynamodb:DeleteItem", "dynamodb:GetItem"],
"Resource": "arn:aws:dynamodb:*:*:table/my-lock-table"
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: confirm principal can reach and write both lock stores
import (
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
)
func preflightLockStores(ctx context.Context, s3c *s3.Client, ddbs *dynamodb.Client, bucket, lockKey, table, lockID string) error {
if _, err := s3c.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &bucket, Key: &lockKey}); err != nil {
return fmt.Errorf("s3 preflight: %w", err)
}
if _, err := ddbs.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {
return fmt.Errorf("dynamodb preflight: %w", err)
}
return nil
} Try / catch
// Unlock can return a *statemgr.LockError; inspect .Err and .Info
import "github.com/hashicorp/terraform/statemgr"
err := client.Unlock(id)
if le, ok := err.(*statemgr.LockError); ok {
log.Printf("unlock failed: %v (held by %s)", le.Err, le.Info.Who)
// branch on wrapped error codes before retrying
} Prevention
- Scope a single IAM policy to grant both s3 and dynamodb lock actions so neither side fails in isolation.
- Run a pre-apply preflight that HeadObjects the lock file and DescribeTables the lock table.
- Use the same region/endpoint for S3 and DynamoDB to avoid split-permission failures.
- Automate force-unlock only after confirming the lock ID matches the stored one.
When it happens
Trigger: Calling Unlock(id) on an S3 backend configured with both useLockFile=true and a dynamodb_table, while the IAM principal lacks s3:DeleteObject on the .tflock key AND dynamodb:DeleteItem on the table, or while both AWS services are unreachable (e.g. network partition, expired STS credentials).
Common situations: Running 'terraform force-unlock' or an apply/destroy cleanup after the IAM policy was tightened, an STS session expired mid-operation, a bucket policy started denying the principal, or a shared CI role lost access to one of the two resources. Also seen during cross-account setups where one role has S3 access but not DynamoDB.
Related errors
- failed to unlock S3: %v
- failed to unlock DynamoDB: %v
- unable to retrieve file from S3 bucket '%s' with key '%s': %
- failed to delete the lock file: %w
- failed to retrieve lock info for lock ID %q: %s
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/1df679d7381be333.
Report an issue: GitHub.