juanfont/headscale · error

parsing policy file: %w

Error message

parsing policy file: %w

What it means

Thrown in `headscale policy set --bypass...` when policy.NewPolicyManager(policyBytes, users, nodes) rejects the policy HuJSON. In bypass mode the CLI pre-validates structure and user references (the boot path, which deliberately skips test evaluation) before writing, so this fires on malformed HuJSON or references to nonexistent users.

Source

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

		if err != nil {
			return fmt.Errorf("reading policy file: %w", err)
		}

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

			users, err := d.ListUsers(nil)
			if err != nil {
				return fmt.Errorf("loading users for policy validation: %w", err)
			}

			_, err = policy.NewPolicyManager(policyBytes, users, views.Slice[types.NodeView]{})
			if err != nil {
				return fmt.Errorf("parsing policy file: %w", err)
			}

			_, err = d.SetPolicy(string(policyBytes))
			if err != nil {
				return fmt.Errorf("setting ACL policy: %w", err)
			}
		} else {
			policyStr := string(policyBytes)

			err := withClient(func(ctx context.Context, client *clientv1.ClientWithResponses) error {
				resp, err := client.SetPolicyWithResponse(ctx, clientv1.SetPolicyJSONRequestBody{
					Policy: &policyStr,
				})
				if err != nil {
					return fmt.Errorf("setting ACL policy: %w", err)
				}

				if resp.StatusCode() != http.StatusOK {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the wrapped error — the v2 policy parser reports the exact path and position of the offending element.
  2. Cross-check every user/email in the policy against `headscale users list`.
  3. Run `headscale policy check --file <path>` against the live server to iterate faster before re-trying set.
  4. Validate HuJSON syntax with a HuJSON-aware formatter.

Example fix

// before: references a user that was deleted
{"grants":[{"src":["old-user@"],"dst":["tag:server"],"ip":["*"]}]}

// after
{"grants":[{"src":["current-user@"],"dst":["tag:server"],"ip":["*"]}]}
Defensive patterns

Strategy: validation

Validate before calling

// cheap pre-parse before the CLI's full validation
func hujsonParses(b []byte) error {
    var v any
    return json.Unmarshal(b, &v) // HuJSON with comments will fail; strip comments first or use hujson tooling
}

Try / catch

_, err := policy.NewPolicyManager(policyBytes, users, views.Slice[types.NodeView]{})
if err != nil {
    // parser error names path+position: fix file, re-run; do NOT fall back to writing unvalidated bytes to the DB
    return err
}

Prevention

When it happens

Trigger: Syntax errors in the HuJSON (missing comma, unbalanced braces); a "user" field naming a user that does not exist in the database; unknown autogroup/tag syntax; invalid CIDR in address rules; tag referenced without an owner.

Common situations: Editing policy by hand and referencing a deleted or renamed user; merging policies from another tailnet; trailing commas accepted in some HuJSON tooling but not others; policy written against a newer headscale syntax than the installed version.

Related errors


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