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

  1. If the error is not_found, the key is already gone — re-run discovery; the cluster delete can proceed.
  2. Ensure the HCLOUD_TOKEN has write scope (read-only tokens cannot delete resources).
  3. Avoid concurrent kops delete runs; serialize cleanup operations against the same cluster.
  4. 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

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


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