hashicorp/terraform · error
failed to empty bucket
Error message
failed to empty bucket %v: %v
What it means
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.
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.
Example fix
// before: policy omits cos:GetBucket, recursive delete fails to empty
// after: add list permission
{
"statement":[{"effect":"allow","action":["cos:GetBucket","cos:DeleteObject","cos:DeleteBucket"],"resource":["qcs::cos:<region>::<bucket>-<appid>","qcs::cos:<region>::<bucket>-<appid>/*"]}]
} Defensive patterns
Strategy: validation
Validate before calling
// Before recursive delete, confirm list permission on the bucket
func canListBucket(ctx context.Context, client *cos.Client, bucket string) error {
_, rsp, err := client.Bucket.Get(ctx, &cos.BucketGetOptions{Prefix: ""})
if rsp == nil { return err }
defer rsp.Body.Close()
if rsp.StatusCode == 403 { return fmt.Errorf("principal lacks cos:GetBucket; cannot empty bucket %s", bucket) }
return nil
} Type guard
func isListForbidden(status int) bool { return status == 403 } Try / catch
// If emptying fails with a permission error, surface a clear message
if rsp != nil && rsp.StatusCode == 403 {
return backoff.Permanent(fmt.Errorf("grant cos:GetBucket to empty bucket %s: %w", bucket, err))
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bucket not exists
- failed to delete bucket
- failed to create bucket
- failed to create tag
- failed to delete tag
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/bdc805d40f5bda14.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/cos/client.go:333
}
if err != nil {
return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err)
}
return nil
}
// deleteBucket delete cos bucket
func (c *remoteClient) deleteBucket(recursive bool) error {
if recursive {
obs, err := c.getBucket("")
if err != nil {
if strings.Contains(err.Error(), "not exists") {
return nil
}
log.Printf("[DEBUG] deleteBucket %s: empty bucket error: %v", c.bucket, err)
return fmt.Errorf("failed to empty bucket %v: %v", c.bucket, err)
}
for _, v := range obs {
c.deleteObject(v.Key)
}
}
rsp, err := c.cosClient.Bucket.Delete(c.cosContext)
if rsp == nil {
log.Printf("[DEBUG] deleteBucket %s: error: %v", c.bucket, err)
return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err)
}
defer rsp.Body.Close()
log.Printf("[DEBUG] deleteBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err)
if rsp.StatusCode == 404 {
return nil
}
View on GitHub (pinned to d32a084675)