juanfont/headscale · error

loading ACL policy: %w

Error message

loading ACL policy: %w

What it means

Thrown in `headscale policy get` (server mode) when client.GetPolicyWithResponse() fails at the transport level — the HTTP request to the headscale API never completed. This is distinct from the apiError branch which handles non-200 responses.

Source

Thrown at cmd/headscale/cli/policy.go:96

		if bypass, _ := cmd.Flags().GetBool(bypassFlag); bypass {
			d, err := openBypassDB(cmd)
			if err != nil {
				return err
			}
			defer d.Close()

			pol, err := d.GetPolicy()
			if err != nil {
				return fmt.Errorf("loading policy from database: %w", err)
			}

			policyData = pol.Data
		} else {
			err := withClient(func(ctx context.Context, client *clientv1.ClientWithResponses) error {
				resp, err := client.GetPolicyWithResponse(ctx)
				if err != nil {
					return fmt.Errorf("loading ACL policy: %w", err)
				}

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

				policyData = resp.JSON200.Policy

				return nil
			})
			if err != nil {
				return err
			}
		}

		// This does not pass output format as we don't support yaml, json or
		// json-line output for this command. It is HuJSON already.
		fmt.Println(policyData)

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Check the server is up: `curl -v <server_url>/api/v1/policy`.
  2. Verify the configured server address and API key in the CLI environment.
  3. If TLS is the issue, ensure the certificate is trusted by the CLI host or fix server_url to match the cert.
  4. If the server is intentionally down, use the --bypass-server-and-access-database-directly flag instead.
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: server API reachable?
func pingAPI(serverURL string, apiKey string) error {
    req, _ := http.NewRequest("GET", serverURL+"/api/v1/apikey", nil)
    req.Header.Set("Authorization", "Bearer "+apiKey)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    resp.Body.Close()
    if resp.StatusCode >= 500 {
        return fmt.Errorf("server unhealthy: %d", resp.StatusCode)
    }
    return nil
}

Type guard

func isTransportError(err error) bool {
    return err != nil && !strings.Contains(err.Error(), "api error (")
}

Try / catch

err := withClient(func(ctx, c) error { ... GetPolicyWithResponse ... })
if err != nil && isTransportError(err) {
    // network-level: backoff and retry once; if still failing, fall back to --bypass only if server is confirmed down
}

Prevention

When it happens

Trigger: headscale server not running or unreachable at the configured address; wrong port; TLS certificate mismatch; DNS failure; connection refused/reset. Note: an HTTP 4xx/5xx does NOT produce this error — that goes through apiError.

Common situations: CLI run on a machine without access to the server; server_url pointing at stale address after moving the deployment; self-signed cert without the CLI trusting it; firewall dropping the connection.

Related errors


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