juanfont/headscale · error
src=%w
Error message
src=%w
What it means
Thrown in Policy.validate() (hscontrol/policy/v2/types.go:2371) when a tag: alias used as an ACL source is not defined under tagOwners. The underlying error comes from p.TagOwners.Contains(tagOwner) and is wrapped with the prefix "src=" so the combined message reads like 'src=tag not found: "tag:foo"' (or the Contains wording), distinguishing source-position failures from destination ones. This enforces that only tags with declared owners can appear in ACLs.
Source
Thrown at hscontrol/policy/v2/types.go:2371
err = validateAutogroupForSrc(ag)
if err != nil {
errs = append(errs, err)
continue
}
case *Group:
g := src
err := p.Groups.Contains(g)
if err != nil {
errs = append(errs, err)
}
case *Tag:
tagOwner := src
err := p.TagOwners.Contains(tagOwner)
if err != nil {
errs = append(errs, fmt.Errorf("src=%w", err))
}
}
}
for _, dst := range acl.Destinations {
switch h := dst.Alias.(type) {
case *Host:
if !p.Hosts.exist(*h) {
errs = append(errs, fmt.Errorf("%w: %q", ErrHostNotDefined, *h))
}
case *AutoGroup:
err := validateAutogroupSupported(h)
if err != nil {
errs = append(errs, err)
continue
}
err = validateAutogroupForDst(h)View on GitHub (pinned to 565fd254d0)
Solutions
- Add the tag to tagOwners, e.g. "tagOwners": { "tag:prod": ["group:admin"] }
- Remove the tag from the ACL source if it is obsolete
- Verify exact casing and the tag: prefix — the %q in the message shows what was looked up
Example fix
// before
"acls": [{ "action": "accept", "src": ["tag:prod"], "dst": ["*"] }]
// after
"tagOwners": { "tag:prod": ["group:admin"] },
"acls": [{ "action": "accept", "src": ["tag:prod"], "dst": ["*"] }] Defensive patterns
Strategy: validation
Validate before calling
func aclSrcTagsDeclared(p *policyv2.Policy) error {
for _, acl := range p.ACLs {
for _, src := range acl.Sources {
if t, ok := src.(*policyv2.Tag); ok {
if err := p.TagOwners.Contains(t); err != nil { return err }
}
}
}
return nil
} Type guard
func isTag(s string) bool { return strings.HasPrefix(s, "tag:") } Try / catch
if err := pol.Validate(); strings.Contains(err.Error(), "src=") { /* check src tag spelling against tagOwners keys */ } Prevention
- Every tag referenced anywhere (src, dst, via) must be a tagOwners key — register new tags in the same commit
- Tag matching is case-sensitive; standardize on lowercase
When it happens
Trigger: An ACL entry with "src": ["tag:prod"] where "prod" is not a key in the policy's tagOwners map. Note "autogroup:tagged" is fine — this fires only for concrete tags.
Common situations: Deleting a tagOwners entry but leaving the tag in acls; introducing a new tag in ACLs before registering its owners; inconsistent naming (tag:Prod vs tag:prod — matching is case-sensitive).
Related errors
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/d30dd59c96b3e22f.
Report an issue: GitHub.