{"record":{"id":"f19de4fbf0b84b0b","repo":"kubernetes/kops","slug":"error-while-waiting-for-volume-s-to-detach-s","errorCode":null,"errorMessage":"error while waiting for volume %s to detach: %s","messagePattern":"error while waiting for volume (.+?) to detach: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/resources/digitalocean/resources.go","lineNumber":293,"sourceCode":"\n\treturn nil\n}\n\nfunc deleteVolume(cloud fi.Cloud, t *resources.Resource) error {\n\tc := cloud.(do.DOCloud)\n\tvolume := t.Obj.(godo.Volume)\n\tfor _, dropletID := range volume.DropletIDs {\n\t\taction, resp, err := c.VolumeActionService().DetachByDropletID(context.TODO(), volume.ID, dropletID)\n\t\tif err != nil {\n\t\t\tif resp != nil && resp.StatusCode == http.StatusNotFound {\n\t\t\t\t// Volume is already detached, nothing to do.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to detach volume %s: %s\", volume.ID, err)\n\t\t}\n\n\t\tif err := waitForDetach(c, action); err != nil {\n\t\t\treturn fmt.Errorf(\"error while waiting for volume %s to detach: %s\", volume.ID, err)\n\t\t}\n\t}\n\n\t_, err := c.VolumeService().DeleteVolume(context.TODO(), t.ID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete volume: %s, err: %s\", t.ID, err)\n\t}\n\n\treturn nil\n}\n\nfunc deleteRecord(cloud fi.Cloud, domain string, t *resources.Resource) error {\n\tc := cloud.(do.DOCloud)\n\trecord := t.Obj.(godo.DomainRecord)\n\n\t_, err := c.DomainService().DeleteRecord(context.TODO(), domain, record.ID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete record for domain %s: %d\", domain, record.ID)","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/resources/digitalocean/resources.go#L275-L311","documentation":"Fires in deleteVolume when waitForDetach fails after DetachByDropletID was accepted — the volume detach action did not complete in time or errored while waiting, so deletion is aborted to avoid destroying an attached volume.","triggerScenarios":"waitForDetach polls the action status and returns error on action failure, polling API error, or timeout while the detach stays in-progress.","commonSituations":"DO action stuck/failing due to heavy I/O on the volume; polling hit rate limits; temporary API outage during the wait loop.","solutions":["Wait for the detach action to complete in DigitalOcean and retry deletion","Check the volume's droplet attachments via the DO console","If the detach action failed, inspect the action's error in the DO API"],"exampleFix":"// before\n# action stuck \"in-progress\" until timeout\n// after\n# stop the droplet or force-detach manually\ndoctl compute droplet-action power-off <droplet-id>\nkops delete cluster --cloud digitalocean ...","handlingStrategy":"retry","validationCode":"deadline := time.Now().Add(5 * time.Minute)\nfor time.Now().Before(deadline) {\n\ta, _, err := c.ActionsService().Get(context.TODO(), action.ID)\n\tif err != nil { return err }\n\tif a.Status == godo.ActionCompleted { return nil }\n\ttime.Sleep(10 * time.Second)\n}\nreturn errors.New(\"timed out waiting for volume detach\")","typeGuard":null,"tryCatchPattern":"if err := waitForDetach(c, action); err != nil {\n\t// one re-poll before failing\n\ttime.Sleep(30 * time.Second)\n\tif err2 := waitForDetach(c, action); err2 != nil {\n\t\treturn fmt.Errorf(\"error while waiting for volume %s to detach: %w\", volume.ID, err2)\n\t}\n}","preventionTips":["Use generous polling timeouts for volume detach on busy volumes","Back off poll intervals to avoid rate limits","Power off the droplet before detaching if detach repeatedly stalls"],"tags":["digitalocean","volume","timeout"],"backgroundTag":"volume-detach-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}