{"record":{"id":"bdc805d40f5bda14","repo":"hashicorp/terraform","slug":"failed-to-empty-bucket-v-v","errorCode":null,"errorMessage":"failed to empty bucket %v: %v","messagePattern":"failed to empty bucket (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/cos/client.go","lineNumber":333,"sourceCode":"\t}\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create bucket %v: %v\", c.bucket, err)\n\t}\n\n\treturn nil\n}\n\n// deleteBucket delete cos bucket\nfunc (c *remoteClient) deleteBucket(recursive bool) error {\n\tif recursive {\n\t\tobs, err := c.getBucket(\"\")\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"not exists\") {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tlog.Printf(\"[DEBUG] deleteBucket %s: empty bucket error: %v\", c.bucket, err)\n\t\t\treturn fmt.Errorf(\"failed to empty bucket %v: %v\", c.bucket, err)\n\t\t}\n\t\tfor _, v := range obs {\n\t\t\tc.deleteObject(v.Key)\n\t\t}\n\t}\n\n\trsp, err := c.cosClient.Bucket.Delete(c.cosContext)\n\tif rsp == nil {\n\t\tlog.Printf(\"[DEBUG] deleteBucket %s: error: %v\", c.bucket, err)\n\t\treturn fmt.Errorf(\"failed to delete bucket %v: %v\", c.bucket, err)\n\t}\n\tdefer rsp.Body.Close()\n\n\tlog.Printf(\"[DEBUG] deleteBucket %s: code: %d, error: %v\", c.bucket, rsp.StatusCode, err)\n\tif rsp.StatusCode == 404 {\n\t\treturn nil\n\t}\n","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/cos/client.go#L315-L351","documentation":"Raised by deleteBucket(recursive=true) when getBucket(\"\") fails while listing objects prior to bucket deletion. The recursive path enumerates all objects so it can delete them before removing the bucket; if listing fails (and the error is not 'not exists'), the emptying step aborts. The message is 'failed to empty bucket'.","triggerScenarios":"At client.go:326-334: getBucket(\"\") returns an error whose message does not contain 'not exists'. Occurs when listing is denied (no cos:GetBucket), the COS service errors (5xx), or a network error during the destroy-time enumeration.","commonSituations":"Running 'terraform destroy' that also removes the backend bucket; principal has DeleteBucket but not ListBucket; very large bucket where list times out; transient COS 5xx during teardown.","solutions":["Grant the principal 'cos:GetBucket' (list) in addition to delete permissions.","Retry the destroy; transient 5xx/list failures often clear.","If the bucket is huge, manually empty it with coscli/lifecycle rules first, then re-run with the bucket already empty.","Confirm connectivity and credentials are still valid at destroy time (long sessions may see token expiry)."],"exampleFix":"# manually empty then re-run destroy\ncoscli rm -r cos://tf-state-1234567890/\nterraform destroy\n# CAM: ensure cos:GetBucket + cos:DeleteObject + cos:DeleteBucket are granted","handlingStrategy":"validation","validationCode":"// Before recursive delete, confirm list permission.\nfunc canListBucket(ctx context.Context, c *cos.Client) error {\n    _, _, err := c.Bucket.Get(ctx, &cos.BucketGetOptions{Prefix: \"\"})\n    return err\n}","typeGuard":null,"tryCatchPattern":"if err := c.deleteBucket(true); err != nil {\n    if strings.Contains(err.Error(), \"failed to empty bucket\") {\n        // grant cos:GetBucket, or empty manually then retry\n    }\n}","preventionTips":["Grant cos:GetBucket + cos:DeleteObject + cos:DeleteBucket to the destroy principal.","For huge buckets, empty out-of-band first.","Watch for STS token expiry during long destroys."],"tags":["cos","tencent-cloud","bucket","delete","list","destroy","permissions"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}