kubernetes/kops · error

failed to delete droplet: %d, err: %s

Error message

failed to delete droplet: %d, err: %s

What it means

deleteDroplet wraps the godo DropletsService.Delete failure during cluster teardown. It first converts the resource ID string to an int (separate error), then deletes; this error fires when DO refuses the droplet deletion — still-attached resources, permission, or the droplet already gone with a non-404 error.

Source

Thrown at pkg/resources/digitalocean/resources.go:263

			resourceTracker.Blocks = blocks
			resourceTrackers = append(resourceTrackers, resourceTracker)
		}
	}

	return resourceTrackers, nil
}

func deleteDroplet(cloud fi.Cloud, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	dropletID, err := strconv.Atoi(t.ID)
	if err != nil {
		return fmt.Errorf("failed to convert droplet ID to int: %s", err)
	}

	_, err = c.DropletsService().Delete(context.TODO(), dropletID)
	if err != nil {
		return fmt.Errorf("failed to delete droplet: %d, err: %s", dropletID, err)
	}

	return nil
}

func deleteVPC(cloud fi.Cloud, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	_, err := c.VPCsService().Delete(context.TODO(), t.ID)
	if err != nil {
		return fmt.Errorf("failed to delete VPC %s (ID %s): %s", t.Name, t.ID, err)
	}

	return nil
}

func deleteVolume(cloud fi.Cloud, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	volume := t.Obj.(godo.Volume)

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Check the DigitalOcean API token and droplet delete permissions
  2. Retry after transient errors
  3. Verify the droplet ID is valid; 404 means it was already deleted

Example fix

// before
# 403 insufficient scope
// after
export DIGITALOCEAN_ACCESS_TOKEN=<token with droplet:write scope>
kops delete cluster --cloud digitalocean ...
Defensive patterns

Strategy: retry

Validate before calling

droplet, resp, err := c.DropletsService().Get(context.TODO(), dropletID)
if resp != nil && resp.StatusCode == http.StatusNotFound {
	return nil // already deleted
}

Try / catch

if _, err := c.DropletsService().Delete(context.TODO(), dropletID); err != nil {
	var gerr *godo.ErrorResponse
	if errors.As(err, &gerr) && gerr.Response.StatusCode == http.StatusNotFound {
		return nil // idempotent: already gone
	}
	return fmt.Errorf("failed to delete droplet: %d, err: %w", dropletID, err)
}

Prevention

When it happens

Trigger: c.DropletsService().Delete(context.TODO(), dropletID) errors: droplet already gone (404), token lacking write scope (403), network/API failure.

Common situations: Droplet deleted out-of-band then kops delete re-run; expired token after cluster creation; DO API transient failure mid-teardown.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/73b1ce1744b5c9e9. Report an issue: GitHub.