juanfont/headscale · error

invalid action

Error message

invalid action

What it means

Exported sentinel declared in hscontrol/policy/v2/filter.go:20 and raised during policy parsing when a grant's action field is not one of the valid values. In the ACL/grants model, 'action' only admits SSH-rule actions ('accept' or 'check'); anything else fails unmarshaling with this error before any filter compilation happens.

Source

Thrown at hscontrol/policy/v2/filter.go:20

import (
	"errors"
	"fmt"
	"net/netip"
	"slices"
	"strings"
	"time"

	"github.com/juanfont/headscale/hscontrol/types"
	"github.com/juanfont/headscale/hscontrol/util"
	"github.com/rs/zerolog/log"
	"go4.org/netipx"
	"tailscale.com/tailcfg"
	"tailscale.com/types/views"
)

var (
	ErrInvalidAction = errors.New("invalid action")
	errSelfInSources = errors.New("autogroup:self cannot be used in sources")
)

// companionCap pairs a well-known Tailscale capability with its
// companion capability.
type companionCap struct {
	original  tailcfg.PeerCapability
	companion tailcfg.PeerCapability
}

// companionCaps lists certain well-known Tailscale capabilities and
// their companion capability. When a grant includes one of these
// capabilities, Tailscale automatically generates an additional
// [tailcfg.FilterRule] with the companion capability and a nil CapMap value.
// The slice is ordered by the original capability name so that
// generated companion rules are emitted deterministically.
var companionCaps = []companionCap{
	{tailcfg.PeerCapabilityTaildrive, tailcfg.PeerCapabilityTaildriveSharer},

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Change the action to "accept" — deny semantics come from omitting the grant, since the policy is default-deny
  2. For SSH rules that re-check periodically, use "check" with a checkPeriod
  3. Validate the policy with 'headscale policy check' before applying

Example fix

// before
{"src": ["group:admin"], "dst": ["tag:server:22"], "action": "deny"}

// after (omit the grant entirely for deny; use accept/check otherwise)
{"src": ["group:admin"], "dst": ["tag:server:22"], "action": "accept"}
Defensive patterns

Strategy: validation

Validate before calling

func validSSHAction(a string) bool { return a == "accept" || a == "check" }

Prevention

When it happens

Trigger: A policy containing a grants or acls entry with e.g. "action": "deny" or a typo like "accpet". The HuJSON/JSON unmarshal path for grant actions rejects the value (see types_test.go 'invalid action should fail' and TestACL_UnmarshalJSON_InvalidAction).

Common situations: Migrating from a firewall mindset and writing 'deny' actions (Tailscale ACLs are default-deny, only accept/check exist); typo in an IaC/terraform-managed policy; hand-editing HuJSON.

Related errors


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