{"record":{"id":"1df679d7381be333","repo":"hashicorp/terraform","slug":"failed-to-unlock-both-s3-and-dynamodb-s3-error","errorCode":null,"errorMessage":"failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v","messagePattern":"failed to unlock both S3 and DynamoDB: S3 error: (.+?), DynamoDB error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":485,"sourceCode":"\tif !c.useLockFile && c.ddbTable != \"\" {\n\t\tlog.Info(\"Attempting to unlock remote state (DynamoDB only)...\")\n\t\tif err := c.unlockWithDynamoDB(ctx, id, lockErr); err != nil {\n\t\t\tlockErr.Err = err\n\t\t\treturn lockErr\n\t\t}\n\n\t\tlog.Info(\"Unlocked remote state (DynamoDB only)\")\n\t\treturn nil\n\t}\n\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.","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/client.go#L467-L503","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# manually clear both locks after a dual unlock failure\naws s3api delete-object --bucket tf-state-prod --key prod/terraform.tflock.tflock\naws dynamodb delete-item \\\n  --table-name terraform-locks \\\n  --key '{\"LockID\":{\"S\":\"tf-state-prod/prod/terraform.tfstate\"}}'","handlingStrategy":"retry","validationCode":"// Confirm both backends are reachable before attempting dual unlock.\nfunc canDualUnlock(ctx context.Context, s3c *s3.Client, ddb *dynamodb.Client, bucket, table string) error {\n  if _, err := s3c.HeadBucket(ctx, &s3.HeadBucketInput{Bucket: &bucket}); err != nil {\n    return err\n  }\n  if _, err := ddb.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {\n    return err\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":"// After a dual-unlock failure, instruct the operator with both errors and the lock ID.\nif ferr != nil && derr != nil {\n  lockErr.Err = fmt.Errorf(\"failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoDB error: %v; \"+\n    \"run `terraform force-unlock %s` once access is restored\", ferr, derr, id)\n  return lockErr\n}","preventionTips":["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."],"tags":["locking","s3","dynamodb","remote-state","unlock","consistency","credentials"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}