juanfont/headscale · error
action must be specified
Error message
action must be specified
What it means
ErrSSHActionMustBeSpecified in hscontrol/policy/v2/types.go:54 is returned when an SSH rule omits the action field. action determines whether sessions are always accepted ("accept") or re-checked periodically ("check"), and headscale refuses to default it — the field is mandatory.
Source
Thrown at hscontrol/policy/v2/types.go:54
var ErrAutogroupSelfRequiresPerNodeResolution = errors.New("autogroup:self requires per-node resolution and cannot be resolved in this context")
var ErrUndefinedTagReference = errors.New("references undefined tag")
// SSH validation errors.
var (
ErrSSHTagSourceToUserDest = errors.New("tags in SSH source cannot access user-owned devices")
ErrSSHUserDestRequiresSameUser = errors.New("user destination requires source to contain only that same user")
ErrSSHAutogroupSelfRequiresUserSource = errors.New("autogroup:self destination requires source to contain only users or groups, not tags or autogroup:tagged")
ErrSSHTagSourceToAutogroupMember = errors.New("tags in SSH source cannot access autogroup:member (user-owned devices)")
ErrSSHWildcardDestination = errors.New("wildcard (*) is not supported as SSH destination")
ErrSSHCheckPeriodAboveMax = errors.New("is above the max (168h)")
ErrSSHCheckPeriodNegative = errors.New("must be a positive duration")
ErrSSHCheckPeriodOnNonCheck = errors.New("checkPeriod is only valid with action \"check\"")
ErrInvalidLocalpart = errors.New("invalid localpart format, must be localpart:*@<domain>")
ErrSSHUsersMustBeSpecified = errors.New("users must be specified")
ErrSSHUserInvalid = errors.New("is not valid")
ErrSSHAcceptEnvEmpty = errors.New("acceptEnv values cannot be empty")
ErrSSHActionMustBeSpecified = errors.New("action must be specified")
ErrSSHActionInvalid = errors.New("is not a valid action")
ErrSSHDestinationHostAlias = errors.New("invalid dst")
ErrTagNameMustStartWithLetter = errors.New("tag names must start with a letter, after 'tag:'")
ErrGroupMembersCannotBeRecursive = errors.New("group members cannot be recursive")
)
// SSH check period constants per Tailscale docs:
// https://tailscale.com/docs/features/tailscale-ssh#checkperiod
// SaaS imposes no minimum (0s is accepted) so headscale matches.
const (
SSHCheckPeriodDefault = 12 * time.Hour
SSHCheckPeriodMax = 7 * 24 * time.Hour
)
// ACL validation errors.
var (
ErrACLAutogroupSelfInvalidSource = errors.New("autogroup:self can only be used with users, groups, or supported autogroups")
)View on GitHub (pinned to 565fd254d0)
Solutions
- Add "action": "accept" or "action": "check" to the ssh rule
- Pick "check" plus a checkPeriod when you want periodic re-authentication against the policy
- Re-apply the policy and verify
Example fix
// before
{"users": ["user1"], "dst": ["tag:srv:user1"]}
// after
{"action": "accept", "users": ["user1"], "dst": ["tag:srv:user1"]} Defensive patterns
Strategy: validation
Validate before calling
if r.Action == "" { return fmt.Errorf("ssh rule missing action") } Type guard
func hasSSHAction(r SSHRule) bool { return r.Action == "accept" || r.Action == "check" } Try / catch
if errors.Is(err, hpolicy.ErrSSHActionMustBeSpecified) { /* default to accept only if that matches intent */ } Prevention
- Make action explicit in every rule; never rely on defaults
- Use a policy schema with action as a required enum
When it happens
Trigger: A policy ssh block defines users/dst but has no "action" key (or an empty string action caught by the must-be-specified check). Policy validation fails with action must be specified.
Common situations: Writing a first SSH rule from memory and forgetting action; a template or Kubernetes ConfigMap that drops empty-looking keys; converting rules between formats where action was implicit.
Related errors
- must be a positive duration
- checkPeriod is only valid with action "check"
- invalid localpart format, must be localpart:*@<domain>
- users must be specified
- is not valid
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9c5ce1f843a75115.
Report an issue: GitHub.