{"record":{"id":"fa7ab2c8f40cebe1","repo":"kubernetes/kops","slug":"failed-to-delete-record-for-domain-s-d","errorCode":null,"errorMessage":"failed to delete record for domain %s: %d","messagePattern":"failed to delete record for domain (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/resources/digitalocean/resources.go","lineNumber":311,"sourceCode":"\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)\n\t}\n\n\treturn nil\n}\n\nfunc deleteLoadBalancer(cloud fi.Cloud, t *resources.Resource) error {\n\tc := cloud.(do.DOCloud)\n\tlb := t.Obj.(godo.LoadBalancer)\n\t_, err := c.LoadBalancersService().Delete(context.TODO(), lb.ID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete load balancer with name %s %v\", lb.Name, err)\n\t}\n\n\treturn nil\n}\n\nfunc waitForDetach(cloud do.DOCloud, action *godo.Action) error {\n\ttimeout := time.After(10 * time.Second)","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/resources/digitalocean/resources.go#L293-L329","documentation":"deleteRecord wraps any error from the DigitalOcean Domains.DeleteRecord API (deleting a DNS record by domain name and record ID). The wrapper discards the underlying error and only formats the domain and record ID, so the real cause (network, auth, not-found, rate limit) is lost. It is thrown during kOps cluster teardown while removing cluster DNS records.","triggerScenarios":"godo DomainService().DeleteRecord returns any error: record already deleted (404), invalid OAuth token (401), domain no longer exists (404), rate limiting (429), or network failure.","commonSituations":"Retrying a partially-failed cluster delete so the DNS record was already removed; expired/revoked doctl API token; domain zone deleted out-of-band before record deletion; DO API rate limits during large teardowns.","solutions":["Check the DO API token (doctl auth list / DIGITALOCEAN_ACCESS_TOKEN) is valid and has write scope, then retry the delete","List existing records for the domain (doctl compute domain records list <domain>) to confirm the record still exists; if already gone, rerun kOps delete and the tracker is refreshed","Check DO API status/rate limits if errors are 429, and retry after backoff","Improve the error message to include the wrapped err (see exampleFix) to diagnose future occurrences"],"exampleFix":"// before\nreturn fmt.Errorf(\"failed to delete record for domain %s: %d\", domain, record.ID)\n// after\nreturn fmt.Errorf(\"failed to delete record %d for domain %s: %v\", record.ID, domain, err)","handlingStrategy":"try-catch","validationCode":"// Before deleting, verify the record still exists and the token works\nrecords, _, err := c.DomainService().Records(context.TODO(), domain)\nif err != nil {\n\treturn fmt.Errorf(\"cannot list records for domain %s: %v\", domain, err)\n}\nfound := false\nfor _, r := range records {\n\tif r.ID == record.ID {\n\t\tfound = true\n\t}\n}\nif !found {\n\tklog.V(2).Infof(\"record %d already gone for domain %s\", record.ID, domain)\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"// Wrap and preserve cause; tolerate already-deleted\n_, err := c.DomainService().DeleteRecord(context.TODO(), domain, record.ID)\nif err != nil {\n\tif gerr, ok := err.(*godo.ErrorResponse); ok && gerr.Response.StatusCode == http.StatusNotFound {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"failed to delete record %d for domain %s: %v\", record.ID, domain, err)\n}","preventionTips":["Always wrap API errors with %v/%w so root causes are diagnosable","Treat HTTP 404 as success in idempotent delete paths","Verify API token validity/scope before starting cluster teardown","Retry delete operations with backoff; they are idempotent"],"tags":["digitalocean","dns","api-error","destructive-ops"],"backgroundTag":"cloud-resource-delete-failed","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}