{"record":{"id":"e7835d64bb5f5a87","repo":"kubernetes/kops","slug":"failed-to-delete-ssh-key-s-s-w","errorCode":null,"errorMessage":"failed to delete ssh key %s(%s): %w","messagePattern":"failed to delete ssh key (.+?)\\((.+?)\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/resources/hetzner/resources.go","lineNumber":231,"sourceCode":"\t\tif volume.Server != nil {\n\t\t\tresourceTracker.Blocked = append(resourceTracker.Blocked, fmt.Sprintf(\"%s:%d\", resourceTypeServer, volume.Server.ID))\n\t\t}\n\n\t\tresourceTrackers = append(resourceTrackers, resourceTracker)\n\t}\n\n\treturn resourceTrackers, nil\n}\n\nfunc deleteSSHKey(cloud fi.Cloud, r *resources.Resource) error {\n\tklog.Infof(\"Deleting SSH Key: %s(%s)\", r.Name, r.ID)\n\n\tc := cloud.(hetzner.HetznerCloud)\n\tclient := c.SSHKeyClient()\n\tsshKey := r.Obj.(*hcloud.SSHKey)\n\t_, err := client.Delete(context.TODO(), sshKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete ssh key %s(%s): %w\", r.Name, r.ID, err)\n\t}\n\n\treturn nil\n}\n\nfunc deleteNetwork(cloud fi.Cloud, r *resources.Resource) error {\n\tklog.Infof(\"Deleting Network: %s(%s)\", r.Name, r.ID)\n\n\tc := cloud.(hetzner.HetznerCloud)\n\tclient := c.NetworkClient()\n\tnetwork := r.Obj.(*hcloud.Network)\n\t_, err := client.Delete(context.TODO(), network)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete network %s(%s): %w\", r.Name, r.ID, err)\n\t}\n\n\treturn nil\n}","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/resources/hetzner/resources.go#L213-L249","documentation":"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.","triggerScenarios":"client.Delete(ctx, sshKey) fails: key already deleted (hcloud not_found), token lacks write permission (unauthorized/forbidden), rate limit, or API/network error.","commonSituations":"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.","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."],"exampleFix":"// before: read-only token\nexport HCLOUD_TOKEN=<readonly-token>  // 403 forbidden on delete\n// after: read/write token\nexport HCLOUD_TOKEN=<readwrite-token>\nkops delete cluster --cloud hetzner --yes","handlingStrategy":"try-catch","validationCode":"key, _, err := client.SSHKey.Get(ctx, keyName)\nif err != nil {\n    var hErr hcloud.Error\n    if errors.As(err, &hErr) && hErr.Code == hcloud.ErrorCodeNotFound {\n        return nil // already deleted; skip\n    }\n}\n// also verify token write scope before delete runs","typeGuard":"var hErr hcloud.Error\nif errors.As(err, &hErr) && (hErr.Code == hcloud.ErrorCodeNotFound || hErr.Code == hcloud.ErrorCodeForbidden) {\n    // not_found => idempotent success; forbidden => fix token scope\n}","tryCatchPattern":"_, err := client.Delete(ctx, sshKey)\nvar hErr hcloud.Error\nif errors.As(err, &hErr) {\n    switch hErr.Code {\n    case hcloud.ErrorCodeNotFound:\n        return nil // already gone; treat as success\n    case hcloud.ErrorCodeForbidden:\n        // use a read/write HCLOUD_TOKEN\n    case hcloud.ErrorCodeRateLimitExceeded:\n        time.Sleep(backoff) // then retry\n    }\n}","preventionTips":["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."],"tags":["hetzner","ssh","deletion","api"],"backgroundTag":"cloud-api-auth-failure","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"}