kubernetes/kops · error

failed to delete record for domain %s: %d

Error message

failed to delete record for domain %s: %d

What it means

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.

Source

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

			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)

	_, err := c.DomainService().DeleteRecord(context.TODO(), domain, record.ID)
	if err != nil {
		return fmt.Errorf("failed to delete record for domain %s: %d", domain, record.ID)
	}

	return nil
}

func deleteLoadBalancer(cloud fi.Cloud, t *resources.Resource) error {
	c := cloud.(do.DOCloud)
	lb := t.Obj.(godo.LoadBalancer)
	_, err := c.LoadBalancersService().Delete(context.TODO(), lb.ID)
	if err != nil {
		return fmt.Errorf("failed to delete load balancer with name %s %v", lb.Name, err)
	}

	return nil
}

func waitForDetach(cloud do.DOCloud, action *godo.Action) error {
	timeout := time.After(10 * time.Second)

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Check the DO API token (doctl auth list / DIGITALOCEAN_ACCESS_TOKEN) is valid and has write scope, then retry the delete
  2. 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
  3. Check DO API status/rate limits if errors are 429, and retry after backoff
  4. Improve the error message to include the wrapped err (see exampleFix) to diagnose future occurrences

Example fix

// before
return fmt.Errorf("failed to delete record for domain %s: %d", domain, record.ID)
// after
return fmt.Errorf("failed to delete record %d for domain %s: %v", record.ID, domain, err)
Defensive patterns

Strategy: try-catch

Validate before calling

// Before deleting, verify the record still exists and the token works
records, _, err := c.DomainService().Records(context.TODO(), domain)
if err != nil {
	return fmt.Errorf("cannot list records for domain %s: %v", domain, err)
}
found := false
for _, r := range records {
	if r.ID == record.ID {
		found = true
	}
}
if !found {
	klog.V(2).Infof("record %d already gone for domain %s", record.ID, domain)
	return nil
}

Try / catch

// Wrap and preserve cause; tolerate already-deleted
_, err := c.DomainService().DeleteRecord(context.TODO(), domain, record.ID)
if err != nil {
	if gerr, ok := err.(*godo.ErrorResponse); ok && gerr.Response.StatusCode == http.StatusNotFound {
		return nil
	}
	return fmt.Errorf("failed to delete record %d for domain %s: %v", record.ID, domain, err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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