kubernetes/kops · error

failed to delete server %s(%s): %w

Error message

failed to delete server %s(%s): %w

What it means

Wrapped error from deleteServer: the hcloud ServerClient.DeleteWithResult call failed when kOps deletes a cluster server. Unlike simple deletes, this uses DeleteWithResult and then watches the action via WatchProgress; this specific message only covers the initial DeleteWithResult failure, while a failed action watch surfaces as the raw error from errCh.

Source

Thrown at pkg/resources/hetzner/resources.go:291

		return fmt.Errorf("failed to delete load balancer %s(%s): %w", r.Name, r.ID, err)
	}

	return nil
}

func deleteServer(cloud fi.Cloud, r *resources.Resource) error {
	ctx := context.TODO()

	klog.Infof("Deleting Server: %s(%s)", r.Name, r.ID)

	c := cloud.(hetzner.HetznerCloud)
	serverClient := c.ServerClient()
	actionClient := c.ActionClient()

	server := r.Obj.(*hcloud.Server)
	result, _, err := serverClient.DeleteWithResult(ctx, server)
	if err != nil {
		return fmt.Errorf("failed to delete server %s(%s): %w", r.Name, r.ID, err)
	}

	_, errCh := actionClient.WatchProgress(ctx, result.Action)
	if err := <-errCh; err != nil {
		return err
	}

	return nil
}

func deleteVolume(cloud fi.Cloud, r *resources.Resource) error {
	klog.Infof("Deleting Volume: %s(%s)", r.Name, r.ID)

	c := cloud.(hetzner.HetznerCloud)
	client := c.VolumeClient()
	volume := r.Obj.(*hcloud.Volume)
	_, err := client.Delete(context.TODO(), volume)
	if err != nil {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Inspect the wrapped hcloud error; if it is not-found, the server is already gone and the cluster delete can be retried.
  2. Confirm the API token has server write permissions and is not expired.
  3. Wait for any running server operations (backups, rescues) to finish — locked servers cannot be deleted.
  4. If the initial delete succeeds but WatchProgress errors, rerun the delete; kOps refreshes server state each pass.
Defensive patterns

Strategy: try-catch

Validate before calling

srv, _, err := c.Server.Get(ctx, strconv.Itoa(serverID))
if err != nil { return err }
if srv == nil { return nil } // already deleted
if srv.Status == hcloud.ServerStatusStarting || srv.Locked() { // locked servers can't be deleted
    return fmt.Errorf("server %d is busy/locked (status=%s)", serverID, srv.Status)
}

Type guard

func isHcloudErrorWithCode(err error, code hcloud.ErrorCode) bool {
    var hErr hcloud.Error
    return errors.As(err, &hErr) && hErr.Code == code
}

Try / catch

_, _, err := serverClient.DeleteWithResult(ctx, server)
if err != nil {
    var hErr hcloud.Error
    if errors.As(err, &hErr) && hErr.Code == hcloud.ErrorCodeNotFound {
        return nil // already gone
    }
    if errors.As(err, &hErr) && hErr.Code == hcloud.ErrorCodeRateLimitExceeded {
        return backoffRetry(err)
    }
    return fmt.Errorf("failed to delete server %s(%s): %w", name, id, err)
}

Prevention

When it happens

Trigger: serverClient.DeleteWithResult(ctx, server) returns an error: server already stopped/deleted (not-found), API token invalid, rate limit, or server locked (e.g. rescue/backup in progress).

Common situations: Server was terminated manually in the Hetzner console before kOps delete; Hetzner maintenance/lock on the server; token permissions lacking server write scope.

Related errors


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