{"record":{"id":"2b3e9bd1b858515b","repo":"hashicorp/terraform","slug":"failed-to-unlock-s3-v","errorCode":null,"errorMessage":"failed to unlock S3: %v","messagePattern":"failed to unlock S3: (.+?)","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":490,"sourceCode":"\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.\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 {","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L472-L508","documentation":"Returned by RemoteClient.Unlock in dual-locking mode when only the S3 lock-file deletion failed while the DynamoDB delete succeeded. It wraps the original error from unlockWithFile, which itself may be a GetObject, read, unmarshal, ID-mismatch, or DeleteObject failure. Because DynamoDB unlocked cleanly, only the S3 side needs remediation.","triggerScenarios":"Unlock(id) with useLockFile=true && ddbTable set, where DynamoDB DeleteItem succeeds but S3 GetObject or DeleteObject on the .tflock file returns an error (NoSuchKey, AccessDenied, KMS/MFA mismatch, or a stale lock ID that no longer matches the file contents).","commonSituations":"The .tflock object was already deleted out-of-band (manual cleanup) so GetObject fails; the S3 bucket policy changed; SSE-C customer key no longer matches; or another process raced to delete the lock file first.","solutions":["Read the wrapped S3 error code: NoSuchKey means the file is already gone (safe to ignore), AccessDenied points to IAM/bucket policy.","Confirm the S3 IAM principal has s3:GetObject and s3:DeleteObject on arn:aws:s3:::<bucket>/<lockFilePath>.","If SSE-C is configured, verify the customer key provided to the backend matches the one used to create the lock file.","Manually delete the orphaned .tflock object if the auto-delete keeps failing, then re-run the operation."],"exampleFix":"// before: bucket policy denies DeleteObject on *.tflock\n// after\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"s3:GetObject\", \"s3:DeleteObject\"],\n  \"Resource\": \"arn:aws:s3:::my-state-bucket/*.tflock\"\n}","handlingStrategy":"try-catch","validationCode":"// Confirm s3:DeleteObject + GetObject on the lock key before unlock\nfunc canDeleteLockFile(ctx context.Context, s3c *s3.Client, bucket, lockKey string) error {\n  if _, err := s3c.GetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &lockKey}); err != nil {\n    return fmt.Errorf(\"cannot read lock file: %w\", err)\n  }\n  return nil // DeleteObject grant verified by IAM policy review, not API\n}","typeGuard":null,"tryCatchPattern":"err := client.Unlock(id)\nif le, ok := err.(*statemgr.LockError); ok && strings.Contains(le.Err.Error(), \"failed to unlock S3\") {\n    // only S3 failed; DynamoDB is clean, retry S3 delete or remediate IAM\n}","preventionTips":["Grant s3:GetObject + s3:DeleteObject on the *.tflock prefix in the runner role.","Avoid S3 Object Lock (WORM) on the state bucket, which blocks lock-file deletes.","Watch for bucket policies that explicitly Deny DeleteObject.","Use SSE-S or correct SSE-C keys consistently to avoid GetObject denial."],"tags":["s3","terraform-state","locking","iam","aws"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}