juanfont/headscale · error

%s failed to fetch tailscale status: %w

Error message

%s failed to fetch tailscale status: %w

What it means

A `TailscaleInContainer` client failed to run `tailscale status` inside its container; `errTailscaleStatus(hostname, err)` is the annotated wrapper used by all status-dependent helpers (IPs, FQDN, login state). The wrapped error is the exec failure from dockertest.

Source

Thrown at integration/tsic/tsic.go:80

	errTailscalePingFailed             = errors.New("ping failed")
	errTailscalePingNotDERP            = errors.New("ping not via DERP")
	errTailscaleNotLoggedIn            = errors.New("tailscale not logged in")
	errTailscaleWrongPeerCount         = errors.New("wrong peer count")
	errTailscaleCannotUpWithoutAuthkey = errors.New("cannot up without authkey")
	errInvalidClientConfig             = errors.New("verifiably invalid client config requested")
	errInvalidTailscaleImageFormat     = errors.New("invalid HEADSCALE_INTEGRATION_TAILSCALE_IMAGE format, expected repository:tag")
	errTailscaleImageRequiredInCI      = errors.New("HEADSCALE_INTEGRATION_TAILSCALE_IMAGE must be set in CI for HEAD version")
	errContainerNotInitialized         = errors.New("container not initialized")
	errFQDNNotYetAvailable             = errors.New("FQDN not yet available")
	errCurlEmptyResponseBody           = errors.New("curl returned empty response body")
)

const (
	VersionHead = "head"
)

func errTailscaleStatus(hostname string, err error) error {
	return fmt.Errorf("%s failed to fetch tailscale status: %w", hostname, err)
}

// TailscaleInContainer is an implementation of TailscaleClient which
// sets up a Tailscale instance inside a container.
type TailscaleInContainer struct {
	version  string
	hostname string

	pool      *dockertest.Pool
	container *dockertest.Resource
	network   *dockertest.Network

	// "cache"
	ips  []netip.Addr
	fqdn string

	// optional config
	caCerts           [][]byte

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the wrapped exec error — 'tailscaled not running' vs 'connection refused' points to readiness vs crash.
  2. Wrap status queries in an Eventually/retry loop rather than calling them once.
  3. Check the client container logs for a tailscaled startup crash.
  4. If the binary is missing, rebuild the tailscale image (or set HEADSCALE_INTEGRATION_TAILSCALE_IMAGE).

Example fix

// before
ips, err := client.IPs() // one-shot, races tailscaled startup

// after
var ips []netip.Addr
require.EventuallyWithT(t, func(c *assert.CollectT) {
    var err error
    ips, err = client.IPs()
    assert.NoError(c, err)
}, 30*time.Second, time.Second)
Defensive patterns

Strategy: retry

Try / catch

// All status-dependent helpers should sit inside an Eventually.
require.EventuallyWithT(t, func(c *assert.CollectT) {
    _, err := client.IPs()
    assert.NoError(c, err)
}, 60*time.Second, time.Second)

Prevention

When it happens

Trigger: Any helper that shells out to `tailscale status` while tailscaled is not running yet, the binary is missing in the container, the socket is down, or tailscaled exited with an error.

Common situations: Querying a client immediately after container start before tailscaled is up; container image built without tailscale installed; tailscaled crashed due to bad flags; test teardown raced the query.

Related errors


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