juanfont/headscale · critical

automigrating types.Route: %w

Error message

automigrating types.Route: %w

What it means

Migration-time error: tx.AutoMigrate(&types.Route{}) failed while cleaning up the legacy routes table (the migration first deletes rows with null node_id, then re-aligns the table with the Go struct). Route is deprecated and kept only for migrations, so failure is almost always environmental or schema-drift, not application logic.

Source

Thrown at hscontrol/db/db.go:97

					// Remove any invalid routes associated with a node that does not exist.
					if tx.Migrator().HasTable(&types.Route{}) && tx.Migrator().HasTable(&types.Node{}) { //nolint:staticcheck // SA1019: Route kept for migrations
						err := tx.Exec("delete from routes where node_id not in (select id from nodes)").Error
						if err != nil {
							return err
						}
					}

					// Remove any invalid routes without a node_id.
					if tx.Migrator().HasTable(&types.Route{}) { //nolint:staticcheck // SA1019: Route kept for migrations
						err := tx.Exec("delete from routes where node_id is null").Error
						if err != nil {
							return err
						}
					}

					err := tx.AutoMigrate(&types.Route{}) //nolint:staticcheck // SA1019: Route kept for migrations
					if err != nil {
						return fmt.Errorf("automigrating types.Route: %w", err)
					}

					return nil
				},
				Rollback: func(db *gorm.DB) error { return nil },
			},
			// Add back constraint so you cannot delete preauth keys that
			// is still used by a node.
			{
				ID: "202501311657",
				Migrate: func(tx *gorm.DB) error {
					err := tx.AutoMigrate(&types.PreAuthKey{})
					if err != nil {
						return fmt.Errorf("automigrating types.PreAuthKey: %w", err)
					}

					err = tx.AutoMigrate(&types.Node{})
					if err != nil {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Check the wrapped driver error for the exact DDL failure (permission, lock, type mismatch).
  2. Back up the database, then inspect the routes table schema and reconcile it with types.Route.
  3. Ensure exclusive access during startup migrations (stop other headscale instances).
  4. Restore from backup and upgrade stepwise through supported versions if schema drift is severe.
Defensive patterns

Strategy: try-catch

Try / catch

hsdb, err := db.NewHeadscaleDatabase(cfg)
if err != nil {
    // migrations are transactional; fix the environment (locks/permissions/disk) and restart
    log.Fatal().Err(err).Msg("migration failed; database unchanged - resolve the wrapped error and restart")
}

Prevention

When it happens

Trigger: Upgrading a database where the routes table has an incompatible structure (columns with wrong types, indexes that block DDL), the DB user lacks ALTER privileges, or SQLite file is locked/read-only.

Common situations: Upgrading from an old/forked headscale whose routes schema differs; running migrations on a read-only SQLite file; Postgres user missing ALTER TABLE rights.

Related errors


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