juanfont/headscale · error · ErrInputStartsWithColon
input cannot start with a colon character
Error message
input cannot start with a colon character
What it means
ErrInputStartsWithColon is returned by splitDestinationAndPort (hscontrol/policy/v2/utils.go:71) when the input's only/last colon is the first character, e.g. ":22". There is no destination before the colon, so the split cannot produce a host. It distinguishes this specific typo from the generic missing-colon case.
Source
Thrown at hscontrol/policy/v2/utils.go:17
package v2
import (
"errors"
"fmt"
"net/netip"
"slices"
"strconv"
"strings"
"tailscale.com/tailcfg"
)
// Port parsing errors.
var (
ErrInputMissingColon = errors.New("input must contain a colon character separating destination and port")
ErrInputStartsWithColon = errors.New("input cannot start with a colon character")
ErrInputEndsWithColon = errors.New("input cannot end with a colon character")
ErrInvalidPortRangeFormat = errors.New("invalid port range format")
ErrPortRangeInverted = errors.New("invalid port range: first port is greater than last port")
ErrPortMustBePositive = errors.New("first port must be >0, or use '*' for wildcard")
ErrInvalidPortNumber = errors.New("invalid first integer")
ErrPortNumberOutOfRange = errors.New("port number out of range")
ErrBracketsNotIPv6 = errors.New("square brackets are only valid around IPv6 addresses")
)
// splitDestinationAndPort takes an input string and returns the destination and port as a tuple, or an error if the input is invalid.
// It supports two bracketed IPv6 forms:
// - "[addr]:port" (RFC 3986, e.g. "[::1]:80")
// - "[addr]/prefix:port" (e.g. "[fd7a::1]/128:80,443")
//
// Brackets are only accepted around IPv6 addresses, not IPv4, hostnames, or other alias types.
// Bracket stripping reduces both forms to bare "addr:port" or "addr/prefix:port",
// which the normal [strings.LastIndex] of ":" split handles correctly because
// port strings never contain colons.View on GitHub (pinned to 565fd254d0)
Solutions
- Fix the entry to include a host before the colon, e.g. "tag:web:22"
- If templating produced it, ensure the host variable is non-empty before rendering the policy
- Scan the policy for dst entries matching ^:\d or ^:\*
Example fix
// before "dst": [":22"] // after "dst": ["tag:web:22"]
Defensive patterns
Strategy: validation
Validate before calling
func dstStartsWithHost(dst string) bool { return !strings.HasPrefix(dst, ":") } Try / catch
if errors.Is(err, policyv2.ErrInputStartsWithColon) {
// a templated host rendered empty; fix the variable or hardcode the host
} Prevention
- Never interpolate an empty host variable into a dst
- Check rendered policy output when templates drive dst generation
When it happens
Trigger: A dst entry like ":80" or ":*" — a port with no host. Raised when strings.LastIndex(":") == 0.
Common situations: Variable interpolation producing an empty host ("${host}:22" with empty ${host}); deleting the host part while editing a rule; copy-paste losing the hostname.
Related errors
- input must contain a colon character separating destination
- input cannot end with a colon character
- first port must be >0, or use '*' for wildcard
- invalid first integer
- port number out of range
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/926bc5175dba6703.
Report an issue: GitHub.