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
- Retry the operation; detach actions are often transient failures
- Verify the volume's actual attachments (doctl compute volume list) and detach manually if needed
- Check token has volume action write scope
- 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
- Keep the retry/backoff wrapper for detach actions
- Verify volume attachments before teardown
- Ensure token can perform volume actions
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
- error while waiting for volume %s to detach: %s
- failed to delete volume: %s, err: %s
- failed to get all volumes by region from %s: %v
- volume tag split failed, too few components for tag %q on vo
- error parsing etcd cluster tag %q on volume %q: %v
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/66368e69eddb5a78.
Report an issue: GitHub.