kubernetes/kops · error

failed to detach volume %s: %s

Error message

failed to detach volume %s: %s

What it means

deleteVolume attempts VolumeActionService().DetachByDropletID for each attached droplet; any non-404 detach error is wrapped here. Volume 404 during detach is treated as already-detached and skipped; anything else aborts.

Source

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

	_, 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)
	for _, dropletID := range volume.DropletIDs {
		action, resp, err := c.VolumeActionService().DetachByDropletID(context.TODO(), volume.ID, dropletID)
		if err != nil {
			if resp != nil && resp.StatusCode == http.StatusNotFound {
				// Volume is already detached, nothing to do.
				continue
			}
			return fmt.Errorf("failed to detach volume %s: %s", volume.ID, err)
		}

		if err := waitForDetach(c, action); err != nil {
			return fmt.Errorf("error while waiting for volume %s to detach: %s", volume.ID, err)
		}
	}

	_, err := c.VolumeService().DeleteVolume(context.TODO(), t.ID)
	if err != nil {
		return fmt.Errorf("failed to delete volume: %s, err: %s", t.ID, err)
	}

	return nil
}

func deleteRecord(cloud fi.Cloud, domain string, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	record := t.Obj.(godo.DomainRecord)

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Retry the operation; detach actions are often transient failures
  2. Verify the volume's actual attachments (doctl compute volume list) and detach manually if needed
  3. Check token has volume action write scope
  4. Inspect wrapped error for 409/422 hints about why DO refused the detach

Example fix

// before
# detach refused for stale attachment
// after
doctl compute volume detach <volume-id> --droplet-id <droplet-id>
kops delete cluster --cloud digitalocean ...
Defensive patterns

Strategy: retry

Validate before calling

for _, d := range dropletIDs {
	if _, _, err := c.DropletsService().Get(context.TODO(), d); err != nil {
		return fmt.Errorf("droplet %d not found for detach", d)
	}
}

Try / catch

action, resp, err := c.VolumeActionService().DetachByDropletID(ctx, volume.ID, dropletID)
if err != nil {
	if resp != nil && resp.StatusCode == http.StatusNotFound { continue }
	if isRetryable(err) { retryWithBackoff(...) }
	return fmt.Errorf("failed to detach volume %s: %w", volume.ID, err)
}

Prevention

When it happens

Trigger: DetachByDropletID returns an error with resp.StatusCode != 404: API auth failure, invalid droplet ID, action refused by DO (e.g. volume busy), or network/5xx error.

Common situations: Volume attached to a droplet DO won't detach from (state mismatch); token lacking volume action scope; API outage mid-teardown; stale attachment records.

Related errors


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