juanfont/headscale · error

listing api keys: %w

Error message

listing api keys: %w

What it means

Wrapped error from the 'headscale apikeys list' Cobra subcommand when client.ListApiKeysWithResponse returns a transport-level error — i.e., the HTTP request to the headscale server failed before a response was parsed (connection refused, TLS failure, DNS, timeout, malformed URL).

Source

Thrown at cmd/headscale/cli/api_key.go:50

	deleteAPIKeyCmd.Flags().StringP("prefix", "p", "", "ApiKey prefix")
	deleteAPIKeyCmd.Flags().Uint64P("id", "i", 0, "ApiKey ID")
	apiKeysCmd.AddCommand(deleteAPIKeyCmd)
}

var apiKeysCmd = &cobra.Command{
	Use:     "apikeys",
	Short:   "Handle the Api keys in Headscale",
	Aliases: []string{"apikey", "api"},
}

var listAPIKeys = &cobra.Command{
	Use:     cmdList,
	Short:   "List the Api keys for headscale",
	Aliases: []string{"ls", cmdShow},
	RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error {
		resp, err := client.ListApiKeysWithResponse(ctx)
		if err != nil {
			return fmt.Errorf("listing api keys: %w", err)
		}

		if resp.StatusCode() != http.StatusOK {
			return apiError(resp.StatusCode(), resp.ApplicationproblemJSONDefault)
		}

		apiKeys := resp.JSON200.ApiKeys

		return printListOutput(cmd, apiKeys, func() error {
			rows := make([][]string, 0, len(apiKeys))
			for _, key := range apiKeys {
				expiration := "-"
				if key.Expiration != nil {
					expiration = ColourTime(*key.Expiration)
				}

				var created string
				if key.CreatedAt != nil {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Verify the server is up: 'headscale health' or curl the --address endpoint
  2. Check the CLI configuration (config file or HS_ADDRESS / --address) points at the server's actual bind address
  3. If TLS is enabled, ensure the CLI trusts the server certificate (ca_cert in the socket config or --tls-ca)
  4. Look at the wrapped error text — connection refused vs x509 vs timeout each points to a different fix
Defensive patterns

Strategy: retry

Validate before calling

// before listing, confirm the server endpoint answers
req, _ := http.NewRequest(http.MethodGet, addr+"/health", nil)
if resp, err := http.DefaultClient.Do(req); err != nil || resp.StatusCode != 200 {
	return fmt.Errorf("server not reachable at %s", addr)
}

Type guard

func isTransportError(err error) bool {
	var netErr net.Error
	return errors.As(err, &netErr) || errors.Is(err, context.DeadlineExceeded)
}

Try / catch

resp, err := client.ListApiKeysWithResponse(ctx)
if err != nil {
	if isTransportError(err) { /* retry with backoff once or twice */ }
	return fmt.Errorf("listing api keys: %w", err)
}

Prevention

When it happens

Trigger: Running 'headscale apikeys list' when the gRPC-server is unreachable: wrong --address (default http://127.0.0.1:50443), server not running, TLS cert mismatch, or the CLI not pointed at the right socket.

Common situations: headscale serve/run not started or listening on a different address; httptest/CI environments where the server URL is not wired into the CLI config; self-signed certs without the CA configured; proxy environment variables (http_proxy) hijacking the connection.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/afec5d27673fcc77. Report an issue: GitHub.