{"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":"exception","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/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/cos/client.go#L315-L351","documentation":"Returned by deleteBucket(recursive=true) when listing the bucket's objects via getBucket(\"\") fails with an error whose message does NOT contain the substring 'not exists'. The backend needs to empty the bucket before deleting it; if listing fails for a non-absence reason, it cannot proceed.","triggerScenarios":"Inside deleteBucket, getBucket(\"\") returns an error that is not the bucket-absence error. Typical: 403 on `cos:GetBucket`, a transient 5xx, or a network failure mapped to a non-'not exists' message.","commonSituations":"Running `terraform destroy` against a COS state backend where the principal can delete the bucket but lacks list permission; partial outage of the COS list API; credentials were downgraded between apply and destroy.","solutions":["Inspect the wrapped error for the underlying list failure cause.","Grant `cos:GetBucket` (list) permission to the principal so the bucket can be enumerated for emptying.","For transient failures, retry the destroy.","If you intend to delete the bucket manually, do so from the console after emptying it, then remove the backend resource from config."],"exampleFix":"// before: policy omits cos:GetBucket, recursive delete fails to empty\n// after: add list permission\n{\n  \"statement\":[{\"effect\":\"allow\",\"action\":[\"cos:GetBucket\",\"cos:DeleteObject\",\"cos:DeleteBucket\"],\"resource\":[\"qcs::cos:<region>::<bucket>-<appid>\",\"qcs::cos:<region>::<bucket>-<appid>/*\"]}]\n}","handlingStrategy":"validation","validationCode":"// Before recursive delete, confirm list permission on the bucket\nfunc canListBucket(ctx context.Context, client *cos.Client, bucket string) error {\n    _, rsp, err := client.Bucket.Get(ctx, &cos.BucketGetOptions{Prefix: \"\"})\n    if rsp == nil { return err }\n    defer rsp.Body.Close()\n    if rsp.StatusCode == 403 { return fmt.Errorf(\"principal lacks cos:GetBucket; cannot empty bucket %s\", bucket) }\n    return nil\n}","typeGuard":"func isListForbidden(status int) bool { return status == 403 }","tryCatchPattern":"// If emptying fails with a permission error, surface a clear message\nif rsp != nil && rsp.StatusCode == 403 {\n    return backoff.Permanent(fmt.Errorf(\"grant cos:GetBucket to empty bucket %s: %w\", bucket, err))\n}","preventionTips":["Grant `cos:GetBucket` plus `cos:DeleteObject` to principals that run recursive destroys.","Pre-empty buckets from the console if the principal only has delete-bucket rights.","Scope CAM policies to include the bucket and its objects/*.","Avoid running destroy from a least-privileged CI principal unless its policy is complete."],"tags":["terraform","cos","tencent-cloud","bucket","permissions","delete","recursive","remote-state","go"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}