juanfont/headscale · error

autogroup:self requires per-node resolution and cannot be re

Error message

autogroup:self requires per-node resolution and cannot be resolved in this context

What it means

Returned from alias resolution (hscontrol/policy/v2/types.go:811) when autogroup:self is used where a global, node-independent answer is required. autogroup:self means 'the requesting node's own user', which can only be resolved per-node (e.g. in filter compilation for a specific node); contexts that compile one answer for the whole tailnet cannot honor it.

Source

Thrown at hscontrol/policy/v2/types.go:36

	"go4.org/netipx"
	"tailscale.com/net/tsaddr"
	"tailscale.com/tailcfg"
	"tailscale.com/types/views"
	"tailscale.com/util/multierr"
	"tailscale.com/util/set"
	"tailscale.com/util/slicesx"
)

// Global JSON options for consistent parsing across all struct unmarshaling.
var policyJSONOpts = []json.Options{
	json.DefaultOptionsV2(),
	json.MatchCaseInsensitiveNames(true),
	json.RejectUnknownMembers(true),
}

const Wildcard = Asterix(0)

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")

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Remove autogroup:self from the offending position — check tagOwners and SSH destinations first
  2. In grants srcs it is fine; replace dst usages with autogroup:member, autogroup:tagged, or explicit users/tags
  3. Use 'headscale policy check' to get the exact location

Example fix

// before
"tagOwners": {"tag:app": ["autogroup:self"]}

// after
"tagOwners": {"tag:app": ["group:admins"]}
Defensive patterns

Strategy: validation

Validate before calling

func autogroupSelfAllowedIn(position string) bool { return position == "grants.src" || position == "ssh.src" }

Prevention

When it happens

Trigger: Using autogroup:self in a position resolved globally: as a tag owner, inside SSH destination resolution, in tests evaluation paths that need a flat IP set, or any Resolve() call without per-node context.

Common situations: Copy-pasting an autogroup from a grants src (valid) into tagOwners or an SSH dst (invalid); upgrading policies from examples that used * and reaching for autogroup:self as the replacement everywhere.

Related errors


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