juanfont/headscale · error
reading policy file: %w
Error message
reading policy file: %w
What it means
Thrown by `headscale policy set` when os.ReadFile on the --file flag value fails. The policy HuJSON file cannot be read before any validation or upload happens. The wrapped error is a standard os.PathError naming the exact reason (no such file, permission denied).
Source
Thrown at cmd/headscale/cli/policy.go:132
fmt.Println(policyData)
return nil
},
}
var setPolicy = &cobra.Command{
Use: "set",
Short: "Updates the ACL Policy",
Long: `
Updates the existing ACL Policy with the provided policy. The policy must be a valid HuJSON object.
This command only works when the acl.policy_mode is set to "db", and the policy will be stored in the database.`,
Aliases: []string{"put", "update"},
RunE: func(cmd *cobra.Command, args []string) error {
policyPath, _ := cmd.Flags().GetString("file")
policyBytes, err := os.ReadFile(policyPath)
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)
}View on GitHub (pinned to 565fd254d0)
Solutions
- Check the path in the error's wrapped PathError — use an absolute path.
- Verify readability: `ls -l` and `cat` the file as the same user running the CLI.
- In scripts, expand the path ($HOME/policy.hujson) instead of using ~.
Example fix
# before headscale policy set --file ~/policy.hujson # after headscale policy set --file "$HOME/policy.hujson"
Defensive patterns
Strategy: validation
Validate before calling
func checkPolicyFile(path string) ([]byte, error) {
if path == "" {
return nil, fmt.Errorf("--file is required")
}
abs, err := filepath.Abs(path)
if err != nil {
return nil, err
}
fi, err := os.Stat(abs)
if err != nil {
return nil, fmt.Errorf("policy file missing: %w", err)
}
if fi.IsDir() {
return nil, fmt.Errorf("policy path is a directory: %s", abs)
}
return os.ReadFile(abs)
} Try / catch
policyBytes, err := os.ReadFile(absPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// typo in path: list candidate files, fix path
} else if errors.Is(err, os.ErrPermission) {
// fix ownership/run as file owner
}
return err
} Prevention
- Use absolute paths in automation; expand $HOME explicitly.
- In CI, assert the policy file exists as a lint step before calling the CLI.
- Keep policy files next to the repo that versions them.
When it happens
Trigger: `headscale policy set --file ./policy.hujson` where the path is wrong, a directory, or unreadable; tilde (~) not expanded because the flag value is passed to os.ReadFile literally.
Common situations: Relative path used from a different working directory; file created as root and CLI run as another user; typo in filename; using ~/policy.hujson unexpanded in a script.
Related errors
- loading policy from database: %w
- loading ACL policy: %w
- loading users for policy validation: %w
- parsing policy file: %w
- setting ACL policy: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/f6902bee06d1ebd9.
Report an issue: GitHub.