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
- Read the wrapped error — the v2 policy parser reports the exact path and position of the offending element.
- Cross-check every user/email in the policy against `headscale users list`.
- Run `headscale policy check --file <path>` against the live server to iterate faster before re-trying set.
- 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
- Run `headscale policy check --file X` (server mode) as a CI gate on every policy change.
- Generate policy with a HuJSON formatter to catch syntax errors locally.
- Keep user references generated from `headscale users list` output rather than typed by hand.
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
- unknown field
- test(s) failed
- tags in SSH source cannot access user-owned devices
- user destination requires source to contain only that same u
- autogroup:self destination requires source to contain only u
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/c4b41dbed76590c4.
Report an issue: GitHub.