kubernetes/kops · error
failed to delete ssh key %s(%s): %w
Error message
failed to delete ssh key %s(%s): %w
What it means
Returned by the Hetzner deleteSSHKey resource-deleter when the hcloud SSH key Delete call fails for a specific key during cluster teardown. The message includes the resource name and ID plus the wrapped API error, so the failing key is directly identifiable. Other resources continue deleting; this error is reported per resource.
Source
Thrown at pkg/resources/hetzner/resources.go:231
if volume.Server != nil {
resourceTracker.Blocked = append(resourceTracker.Blocked, fmt.Sprintf("%s:%d", resourceTypeServer, volume.Server.ID))
}
resourceTrackers = append(resourceTrackers, resourceTracker)
}
return resourceTrackers, nil
}
func deleteSSHKey(cloud fi.Cloud, r *resources.Resource) error {
klog.Infof("Deleting SSH Key: %s(%s)", r.Name, r.ID)
c := cloud.(hetzner.HetznerCloud)
client := c.SSHKeyClient()
sshKey := r.Obj.(*hcloud.SSHKey)
_, err := client.Delete(context.TODO(), sshKey)
if err != nil {
return fmt.Errorf("failed to delete ssh key %s(%s): %w", r.Name, r.ID, err)
}
return nil
}
func deleteNetwork(cloud fi.Cloud, r *resources.Resource) error {
klog.Infof("Deleting Network: %s(%s)", r.Name, r.ID)
c := cloud.(hetzner.HetznerCloud)
client := c.NetworkClient()
network := r.Obj.(*hcloud.Network)
_, err := client.Delete(context.TODO(), network)
if err != nil {
return fmt.Errorf("failed to delete network %s(%s): %w", r.Name, r.ID, err)
}
return nil
}View on GitHub (pinned to 4c8573c808)
Solutions
- If the error is not_found, the key is already gone — re-run discovery; the cluster delete can proceed.
- Ensure the HCLOUD_TOKEN has write scope (read-only tokens cannot delete resources).
- Avoid concurrent kops delete runs; serialize cleanup operations against the same cluster.
- Retry on transient 429/5xx errors; verify with the Hetzner console that the key still exists.
Example fix
// before: read-only token export HCLOUD_TOKEN=<readonly-token> // 403 forbidden on delete // after: read/write token export HCLOUD_TOKEN=<readwrite-token> kops delete cluster --cloud hetzner --yes
Defensive patterns
Strategy: try-catch
Validate before calling
key, _, err := client.SSHKey.Get(ctx, keyName)
if err != nil {
var hErr hcloud.Error
if errors.As(err, &hErr) && hErr.Code == hcloud.ErrorCodeNotFound {
return nil // already deleted; skip
}
}
// also verify token write scope before delete runs Type guard
var hErr hcloud.Error
if errors.As(err, &hErr) && (hErr.Code == hcloud.ErrorCodeNotFound || hErr.Code == hcloud.ErrorCodeForbidden) {
// not_found => idempotent success; forbidden => fix token scope
} Try / catch
_, err := client.Delete(ctx, sshKey)
var hErr hcloud.Error
if errors.As(err, &hErr) {
switch hErr.Code {
case hcloud.ErrorCodeNotFound:
return nil // already gone; treat as success
case hcloud.ErrorCodeForbidden:
// use a read/write HCLOUD_TOKEN
case hcloud.ErrorCodeRateLimitExceeded:
time.Sleep(backoff) // then retry
}
} Prevention
- Use a read/write token for delete operations, not read-only.
- Treat not_found as success to keep re-runs idempotent.
- Never run concurrent deletes of the same cluster.
- Verify key existence in the Hetzner console when a delete fails.
When it happens
Trigger: client.Delete(ctx, sshKey) fails: key already deleted (hcloud not_found), token lacks write permission (unauthorized/forbidden), rate limit, or API/network error.
Common situations: Re-running kops delete after a partial deletion (key already gone); token is read-only so DELETE /ssh_keys/{id} returns 403; two delete jobs racing on the same cluster; transient Hetzner API errors.
Related errors
- failed to list ssh keys: %w
- failed to list networks: %w
- failed to list firewalls: %w
- failed to list load balancers: %w
- failed to list servers: %w
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/e7835d64bb5f5a87.
Report an issue: GitHub.