hashicorp/terraform · error

failed to empty bucket %v: %v

Error message

failed to empty bucket %v: %v

What it means

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'.

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 c9def3e214)

Solutions

  1. Grant the principal 'cos:GetBucket' (list) in addition to delete permissions.
  2. Retry the destroy; transient 5xx/list failures often clear.
  3. If the bucket is huge, manually empty it with coscli/lifecycle rules first, then re-run with the bucket already empty.
  4. Confirm connectivity and credentials are still valid at destroy time (long sessions may see token expiry).

Example fix

# manually empty then re-run destroy
coscli rm -r cos://tf-state-1234567890/
terraform destroy
# CAM: ensure cos:GetBucket + cos:DeleteObject + cos:DeleteBucket are granted
Defensive patterns

Strategy: validation

Validate before calling

// Before recursive delete, confirm list permission.
func canListBucket(ctx context.Context, c *cos.Client) error {
    _, _, err := c.Bucket.Get(ctx, &cos.BucketGetOptions{Prefix: ""})
    return err
}

Try / catch

if err := c.deleteBucket(true); err != nil {
    if strings.Contains(err.Error(), "failed to empty bucket") {
        // grant cos:GetBucket, or empty manually then retry
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/bdc805d40f5bda14. Report an issue: GitHub.