juanfont/headscale · error

test(s) failed

Error message

test(s) failed

What it means

Wraps the failure of the policy's 'tests' block assertions (hscontrol/policy/v2/test.go:3228): one or more PolicyTest entries evaluated against the compiled global filter rules did not pass, and the individual failures are joined with multierr underneath this sentinel. It is produced at policy write boundaries — 'headscale policy set', file-mode reload after a change, 'headscale policy check' — so a bad policy never becomes live.

Source

Thrown at hscontrol/policy/v2/test.go:32

	"tailscale.com/types/views"
)

// Tailscale's policy file `tests` block validates a policy against operator
// assertions: from a given src, named dst:port pairs must be accepted, and
// (optionally) other dst:port pairs must be denied. They run at user-write
// boundaries — `headscale policy set`, file-mode reload after a change,
// `headscale policy check` — and reject the write if any assertion fails.
// Boot-time reload of an already-stored policy does not run them, so a
// stale referenced entity (e.g. a deleted user) cannot lock the server out.
//
// The tests evaluate against the compiled global filter rules, which fold in
// both `acls` and `grants`, so the `tests` block validates the whole policy.

// errPolicyTestsFailed and errSSHPolicyTestsFailed share the
// "test(s) failed" prefix but stay distinct so callers can use
// [errors.Is] to tell ACL-test and SSH-test failures apart.
var (
	errPolicyTestsFailed    = errors.New("test(s) failed")
	errSSHPolicyTestsFailed = errors.New("test(s) failed")
	errTestDestinationNoIP  = errors.New("destination resolved to no IP addresses")
)

// PolicyTest is one entry in the policy's `tests` block.
type PolicyTest struct {
	// Src is a single source alias (user, group, tag, host, autogroup, or IP).
	// Tailscale only supports a single src per test entry.
	Src string `json:"src"`

	// Proto restricts the test to one protocol. Empty matches the default
	// set the client applies when proto is omitted (TCP/UDP/ICMP).
	Proto Protocol `json:"proto,omitempty"`

	// Accept lists destinations in `host:port` form that must be reachable
	// from Src. A test fails if any entry is denied by the compiled filter.
	Accept []string `json:"accept,omitempty"`

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the joined per-test errors under the 'test(s) failed:' prefix — each names the failing src and destination
  2. Fix the grants to match the intended assertion, or fix the test to match the actual rules
  3. Iterate with 'headscale policy check -f policy.hujson' until the tests pass, then apply

Example fix

// before: test expects access the grants do not grant
"tests": [{"src": "user-a", "accept": ["tag:web:80", "tag:web:443"]}]

// after: grant it, then keep the test
"grants": [{"src": ["user-a"], "dst": ["tag:web"], "ip": ["tcp/80", "tcp/443"]}]
Defensive patterns

Strategy: try-catch

Try / catch

err := pm.Compile(); if errors.Is(err, errPolicyTestsFailed) { // iterate: print the multierr detail lines, fix grants or tests, re-run 'policy check' }

Prevention

When it happens

Trigger: A policy whose tests block contains an assertion contradicting the acls/grants, e.g. {'src': 'user-a', 'accept': ['tag:web:80']} when no grant actually allows user-a to tag:web on port 80. RunTests collects each failing entry and returns fmt.Errorf wrapping errPolicyTestsFailed.

Common situations: Refactoring grants without updating tests; tests written against stale users/tags after deletions; port/proto mismatch between the test's 'accept' entries and the grants.

Related errors


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