juanfont/headscale · critical

init state: %w

Error message

init state: %w

What it means

state.NewState(cfg) failed during headscale startup. State is the central coordinator (per AGENTS.md, all cross-subsystem operations go through hscontrol/state); its constructor opens the database, runs migrations, seeds policy, and initializes the in-memory NodeStore. The wrapped error identifies which stage broke — most commonly database open/migration (bad db.type/db.path, unreachable PostgreSQL, failed migration) or invalid policy/config values validated at construction time.

Source

Thrown at hscontrol/app.go:125

	tailsqlTSKey     = envknob.String("TS_AUTHKEY")
	dumpConfig       = envknob.Bool("HEADSCALE_DEBUG_DUMP_CONFIG")
)

func NewHeadscale(cfg *types.Config) (*Headscale, error) {
	var err error

	if profilingEnabled {
		runtime.SetBlockProfileRate(1)
	}

	noisePrivateKey, err := readOrCreatePrivateKey(cfg.NoisePrivateKeyPath)
	if err != nil {
		return nil, fmt.Errorf("reading or creating Noise protocol private key: %w", err)
	}

	s, err := state.NewState(cfg)
	if err != nil {
		return nil, fmt.Errorf("init state: %w", err)
	}

	app := Headscale{
		cfg:               cfg,
		noisePrivateKey:   noisePrivateKey,
		clientStreamsOpen: sync.WaitGroup{},
		state:             s,
	}

	if len(cfg.TrustedProxies) > 0 {
		app.realIPMiddleware, err = trustedProxyRealIP(cfg.TrustedProxies)
		if err != nil {
			return nil, fmt.Errorf("building trusted_proxies middleware: %w", err)
		}
	}

	// Initialize ephemeral garbage collector
	ephemeralGC := db.NewEphemeralGarbageCollector(func(ni types.NodeID) {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the wrapped error — 'init state' chains the real cause (db open, migration name, policy parse)
  2. For PostgreSQL: verify connectivity with the same DSN (`psql <dsn> -c 'select 1'`) and credentials
  3. For SQLite: ensure db_path's directory exists and is writable by the headscale user; remove stale lock files from a crashed instance
  4. Restore from backup if a migration fails mid-way; never skip failed migrations (migration rules in AGENTS.md are load-bearing)
Defensive patterns

Strategy: try-catch

Validate before calling

// For PostgreSQL configs, verify connectivity before handing off to headscale
if cfg.Database.Type == "postgres" {
    if err := pingDB(cfg.Database.Postgres.Host, cfg.Database.Postgres.User); err != nil {
        return fmt.Errorf("database unreachable: %w", err)
    }
}

Try / catch

if _, err := state.NewState(cfg); err != nil {
    // unwrapping reveals db open vs migration vs policy validation;
    // migration failures must never be retried with FKs disabled or orders changed
    return fmt.Errorf("init state: %w", err)
}

Prevention

When it happens

Trigger: Configured database unavailable (PostgreSQL connection refused, wrong credentials), SQLite db_path in an unwritable location, a migration failing against a corrupted or partially-upgraded schema, or policy/config validation rejecting values inside NewState.

Common situations: PostgreSQL not started before headscale; db path on a read-only volume; upgrading headscale across versions against an old database whose schema was hand-modified; wrong db.type spelling in config; SQLite file locked by another headscale instance.

Related errors


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