kubernetes/kops · error

failed to get info for server %q: %w

Error message

failed to get info for server %q: %w

What it means

After issuing Shutdown, deleteServer polls client.GetByID up to 30 times waiting for the server to reach status 'off'; this error is thrown when GetByID returns an error or a nil server during that poll loop.

Source

Thrown at upup/pkg/fi/cloudup/hetzner/cloud.go:276

	return err
}

// deleteServer shuts down and deletes the given server
func deleteServer(c *hetznerCloudImplementation, id int64) error {
	client := c.ServerClient()
	ctx := context.TODO()

	server := &hcloud.Server{ID: id}
	_, _, err := client.Shutdown(ctx, server)
	if err != nil {
		return fmt.Errorf("failed to stop server %q: %w", strconv.FormatInt(id, 10), err)
	}

	for i := 1; i <= 30; i++ {
		server, _, err := client.GetByID(ctx, id)
		if err != nil || server == nil {
			return fmt.Errorf("failed to get info for server %q: %w", strconv.FormatInt(id, 10), err)
		}

		if server.Status == hcloud.ServerStatusOff {
			break
		}

		time.Sleep(time.Second * 5)
	}

	_, err = client.Delete(ctx, server)
	if err != nil {
		return fmt.Errorf("failed to delete server %q: %w", strconv.FormatInt(id, 10), err)
	}

	return nil
}

func (c *hetznerCloudImplementation) DetachInstance(instance *cloudinstances.CloudInstance) error {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Re-check whether the server exists; treat nil server as already gone and proceed to delete/reconcile
  2. Back off / add rate-limit handling in polling (the loop sleeps 5s per iteration but can still trip limits)
  3. Retry the whole DeleteInstance operation after transient failures
  4. Verify token validity and network stability during long-running operations

Example fix

// before
if err != nil || server == nil {
	return fmt.Errorf("failed to get info for server %q: %w", strconv.FormatInt(id, 10), err)
}
// after
if err != nil {
	return fmt.Errorf("failed to get info for server %q: %w", strconv.FormatInt(id, 10), err)
}
if server == nil {
	return nil // server already gone, nothing to delete
}
Defensive patterns

Strategy: retry

Validate before calling

// before polling, confirm the server exists and is shutting down
server, _, err := client.GetByID(ctx, id)
if err == nil && server != nil && server.Status == hcloud.ServerStatusOff {
	return nil
}

Type guard

func serverReachedStatus(client *hcloud.ServerClient, id int64, want hcloud.ServerStatus) (bool, error) {
	s, _, err := client.GetByID(context.TODO(), id)
	if err != nil {
		return false, err
	}
	if s == nil {
		return true, nil // gone: nothing to wait for
	}
	return s.Status == want, nil
}

Try / catch

server, _, err := client.GetByID(ctx, id)
if err != nil {
	var hErr *hcloud.Error
	if errors.As(err, &hErr) && hErr.Code == hcloud.ErrorCodeRateLimit {
		time.Sleep(hErr.RateLimit.RetryAfter())
		continue
	}
	return fmt.Errorf("failed to get info for server %q: %w", strconv.FormatInt(id, 10), err)
}

Prevention

When it happens

Trigger: The server is deleted out-of-band during the wait (GetByID returns nil), the API token becomes invalid mid-loop, rate limiting kicks in after repeated polling, or a network error occurs while polling.

Common situations: Slow shutdown exceeding the 150s poll window combined with an external deletion, hcloud API flakiness, or rate limit from frequent GetByID calls on large rolling updates.

Related errors


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