juanfont/headscale · critical

automigrating types.Node: %w

Error message

automigrating types.Node: %w

What it means

Migration 202501311657 failed at tx.AutoMigrate(&types.Node{}) - the second step that re-aligns the nodes table with the current Go struct (part of the same migration that restores the pre-auth-key FK). Failure means DDL on the nodes table was rejected: locks, permissions, or an incompatible existing column type.

Source

Thrown at hscontrol/db/db.go:116

					}

					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 {
						return fmt.Errorf("automigrating types.Node: %w", err)
					}

					return nil
				},
				Rollback: func(db *gorm.DB) error { return nil },
			},
			// Ensure there are no nodes referring to a deleted preauthkey.
			{
				ID: "202502070949",
				Migrate: func(tx *gorm.DB) error {
					if tx.Migrator().HasTable(&types.PreAuthKey{}) {
						err := tx.Exec(`
UPDATE nodes
SET auth_key_id = NULL
WHERE auth_key_id IS NOT NULL
AND auth_key_id NOT IN (
    SELECT id FROM pre_auth_keys
);

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Free disk space at least equal to the nodes table size (SQLite needs room for the rewrite).
  2. Terminate blocking sessions / stop the old headscale process before restarting.
  3. Inspect the wrapped error for the specific column/DDL that failed and compare against types.Node.
  4. Restore the pre-migration backup if the migration aborted mid-transaction.
Defensive patterns

Strategy: try-catch

Try / catch

if _, err := db.NewHeadscaleDatabase(cfg); err != nil {
    if strings.Contains(err.Error(), "automigrating types.Node") {
        // DDL on nodes failed: free disk (SQLite table rewrite), clear locks, restart
    }
}

Prevention

When it happens

Trigger: AutoMigrate attempts to alter nodes while another connection holds a lock; a nodes column type changed between versions in a way SQLite cannot remap; disk full during table rewrite (SQLite alters copy the table).

Common situations: Large nodes table with insufficient disk for SQLite's copy-on-alter; Postgres ACCESS EXCLUSIVE lock blocked by a long-running query; upgrading a database that a forked build modified.

Related errors


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