{"record":{"id":"d94905ca8cab1e4e","repo":"hashicorp/terraform","slug":"failed-to-delete-the-lock-file-w","errorCode":null,"errorMessage":"failed to delete the lock file: %w","messagePattern":"failed to delete the lock file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":553,"sourceCode":"\tlockInfo := &statemgr.LockInfo{}\n\tif err := json.Unmarshal(data, lockInfo); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal JSON data into LockInfo struct: %w\", err)\n\t}\n\tlockErr.Info = lockInfo\n\n\t// Verify that the provided lock ID matches the lock ID of the retrieved lock file.\n\tif lockInfo.ID != id {\n\t\treturn fmt.Errorf(\"lock ID '%s' does not match the existing lock ID '%s'\", id, lockInfo.ID)\n\t}\n\n\t// Delete the lock file to release the lock.\n\t_, err = c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{\n\t\tBucket: aws.String(c.bucketName),\n\t\tKey:    aws.String(c.lockFilePath),\n\t})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete the lock file: %w\", err)\n\t}\n\n\tlog.Debug(fmt.Sprintf(\"Deleted lock file: '%q'\", c.lockFilePath))\n\n\treturn nil\n}\n\nfunc (c *RemoteClient) unlockWithDynamoDB(ctx context.Context, id string, lockErr *statemgr.LockError) error {\n\t// TODO: store the path and lock ID in separate fields, and have proper\n\t// projection expression only delete the lock if both match, rather than\n\t// checking the ID from the info field first.\n\tlockInfo, err := c.getLockInfoWithDynamoDB(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve lock info for lock ID %q: %s\", id, err)\n\t}\n\tlockErr.Info = lockInfo\n\n\tif lockInfo.ID != id {","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L535-L571","documentation":"Thrown by unlockWithFile after the lock ID is verified and the code calls s3Client.DeleteObject on the .tflock key but AWS returns an error. The lock was successfully validated but could not be removed, so the lock effectively remains.","triggerScenarios":"Unlock -> unlockWithFile where c.s3Client.DeleteObject fails on the verified lock file: AccessDenied, bucket policy Deny, object in Glacier deep archive, KMS denial, or a transient AWS error.","commonSituations":"IAM principal has GetObject but not DeleteObject; a bucket policy explicitly denies deletes; object-locked bucket (WORM) prevents deletion; region mismatch; or a service control policy blocking deletes.","solutions":["Confirm s3:DeleteObject permission on arn:aws:s3:::<bucket>/<lockFilePath>.","Check the bucket is not in S3 Object Lock / WORM mode that blocks deletes.","Review bucket policies and SCPs for explicit Deny on DeleteObject.","As a last resort, use lifecycle rules or versioning suspend to release the lock, or delete via a privileged account."],"exampleFix":"// before: only GetObject granted\n// after\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"s3:GetObject\", \"s3:DeleteObject\"],\n  \"Resource\": \"arn:aws:s3:::my-state-bucket/*\"\n}","handlingStrategy":"validation","validationCode":"// IAM-side check: ensure DeleteObject is granted (simulate or use IAM evaluator)\n// Runtime pre-flight: try deleting a no-op test object under the same prefix\nfunc canDeleteInPrefix(ctx context.Context, s3c *s3.Client, bucket string) error {\n  probe := \"tflock-probe-\" + uuid.NewString()\n  if _, err := s3c.PutObject(ctx, &s3.PutObjectInput{Bucket: &bucket, Key: &probe, Body: strings.NewReader(\"\")}); err != nil { return err }\n  _, err := s3c.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &bucket, Key: &probe})\n  return err\n}","typeGuard":null,"tryCatchPattern":"if err := client.Unlock(id); err != nil {\n    var ae smithy.APIError\n    if errors.As(err, &ae) && (ae.ErrorCode()==\"AccessDenied\" || ae.ErrorCode()==\"AccessDeniedException\") {\n        // surface IAM remediation guidance\n    }\n}","preventionTips":["Grant s3:DeleteObject on the state bucket prefix in the runner role.","Avoid S3 Object Lock / WORM on the state bucket.","Review bucket policies and SCPs for explicit Deny on deletes.","Test delete capability with a probe object in CI setup."],"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"}