juanfont/headscale · error

headscale exited: %w

Error message

headscale exited: %w

What it means

"headscale exited: %w" at cmd/dev/main.go:234 wraps serve.Wait() after the dev workflow finishes its setup and prints its banner. It fires when the spawned `headscale serve` process exits with a non-zero status for a reason other than Ctrl+C (ctx cancellation is checked first and returns nil). The child's stdout/stderr were forwarded, so the crash reason is already on the console.

Source

Thrown at cmd/dev/main.go:234

		*port, metricsPort, metricsPort,
		configPath, tmpDir,
		authKey,
		*port, authKey,
		hsBin, configPath,
		hsBin, configPath,
	)

	// Wait for headscale to exit.
	err = serve.Wait()
	if err != nil {
		// Context cancellation is expected on Ctrl+C.
		if ctx.Err() != nil {
			fmt.Println("\nShutting down...")

			return nil
		}

		return fmt.Errorf("headscale exited: %w", err)
	}

	return nil
}

// waitForHealth polls the health endpoint until it returns 200 or the
// timeout expires.
func waitForHealth(ctx context.Context, url string, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)

	for time.Now().Before(deadline) {
		if ctx.Err() != nil {
			return ctx.Err()
		}

		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			return fmt.Errorf("creating request: %w", err)

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Scroll to the child's own panic/error output — the %w cause is usually just 'exit status 1/2'
  2. If a panic, fix the reported stack in hscontrol; re-run cmd/dev to reproduce
  3. If OOM/signal, free resources or stop whatever sent the signal, then re-run
Defensive patterns

Strategy: try-catch

Try / catch

if err := serve.Wait(); err != nil {
	if ctx.Err() != nil {
		return nil // Ctrl+C: expected shutdown, not an error
	}
	// child's panic/stack was already forwarded to stdout — read it there
	return fmt.Errorf("headscale exited: %w", err)
}

Prevention

When it happens

Trigger: The server crashing at runtime after a healthy start: fatal runtime error after a config reload, database corruption, panic in a handler, port conflict introduced mid-run, or SIGTERM from outside. Not returned for clean Ctrl+C shutdown.

Common situations: Server panics on a code path under development (this is a dev harness); OOM killer terminating the process; another service binding the same port after startup.

Related errors


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