juanfont/headscale · critical

running migration 202501311657: %w

Error message

running migration 202501311657: %w

What it means

On sqlite, headscale first migrates up to and including migration '202501311657' with foreign keys disabled — the last migration permitted to run FK-off (the set is frozen as of 2025-07-02). This error means one of those early migrations failed during MigrateTo.

Source

Thrown at hscontrol/db/db.go:1175

	)
}

func runMigrations(cfg types.DatabaseConfig, dbConn *gorm.DB, migrations *gormigrate.Gormigrate) error {
	if cfg.Type == types.DatabaseSqlite {
		// SQLite: Run the early migrations that GORM cannot handle safely with
		// foreign keys enabled (route and pre-auth-key automigrations) with FK
		// disabled, then run everything else with FK enabled.
		//
		// NO NEW MIGRATIONS SHOULD RUN WITH FK DISABLED. As of 2025-07-02, all
		// new migrations must run with foreign keys enabled via the
		// migrations.Migrate() call below.
		if err := dbConn.Exec("PRAGMA foreign_keys = OFF").Error; err != nil { //nolint:noinlineerr
			return fmt.Errorf("disabling foreign keys: %w", err)
		}

		// Run up to and including the last migration that requires FK disabled.
		if err := migrations.MigrateTo("202501311657"); err != nil { //nolint:noinlineerr
			return fmt.Errorf("running migration 202501311657: %w", err)
		}

		if err := dbConn.Exec("PRAGMA foreign_keys = ON").Error; err != nil { //nolint:noinlineerr
			return fmt.Errorf("restoring foreign keys: %w", err)
		}

		// Run the rest of the migrations
		if err := migrations.Migrate(); err != nil { //nolint:noinlineerr
			return err
		}

		// Check for constraint violations at the end
		type constraintViolation struct {
			Table           string
			RowID           int
			Parent          string
			ConstraintIndex int
		}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the chained gormigrate error to identify the failing early migration.
  2. Validate the sqlite file: sqlite3 headscale.db 'PRAGMA integrity_check;'.
  3. Restore from a known-good backup or litestream replica if integrity fails.
  4. For a fresh start, move the DB file aside and let headscale recreate it, then re-register nodes.
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-migration integrity check for sqlite:
// sqlite3 headscale.db 'PRAGMA integrity_check;'   # expect: ok
// sqlite3 headscale.db 'SELECT * FROM schema_migrations;' # inspect version state

Try / catch

// On failure, fall back to the most recent backup/replica:
// restore, verify integrity, restart headscale. Do not hand-edit
// schema_migrations to 'skip' a migration.

Prevention

When it happens

Trigger: An existing database whose early schema disagrees with the migration scripts (schema_migrations records a version but the tables were altered), or a lock/corruption issue during the early phase. Also fires on fresh DBs if InitSchema's AutoMigrate step fails.

Common situations: Database restored from a partial backup; schema edited by external tooling; sqlite file truncated. Distinct from later-migration failures (error 400/401 path) because these run with FKs off.

Related errors


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