juanfont/headscale · error

storing database version: %w

Error message

storing database version: %w

What it means

After all migrations succeed, headscale records the running binary's version in the database (setDatabaseVersion) so future upgrades can decide what to migrate. Dev builds skip this deliberately. This error means migrations completed but the version bookkeeping write failed — the schema is fine, the metadata write is not.

Source

Thrown at hscontrol/db/db.go:1004

			}
		}

		return nil
	})

	err = runMigrations(cfg.Database, dbConn, migrations)
	if err != nil {
		return nil, fmt.Errorf("migration failed: %w", err)
	}

	// Store the current version in the database after migrations succeed.
	// Dev builds skip this to preserve the stored version for the next
	// real versioned binary.
	currentVersion := types.GetVersionInfo().Version
	if !isDev(currentVersion) {
		err = setDatabaseVersion(dbConn, currentVersion)
		if err != nil {
			return nil, fmt.Errorf(
				"storing database version: %w",
				err,
			)
		}
	}

	// Validate that the schema ends up in the expected state.
	// This is currently only done on sqlite as squibble does not
	// support Postgres and we use our sqlite schema as our source of
	// truth.
	if cfg.Database.Type == types.DatabaseSqlite {
		sqlConn, err := dbConn.DB()
		if err != nil {
			return nil, fmt.Errorf("getting DB from gorm: %w", err)
		}

		// or else it blocks...
		sqlConn.SetMaxIdleConns(maxIdleConns)

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Grant the headscale DB user INSERT/UPDATE on the database (or at minimum the version table) and restart.
  2. Check the chained error for the exact SQL problem; verify the table setDatabaseVersion writes to exists.
  3. If using sqlite, confirm the file and directory are writable by the headscale process user.
  4. Safe to simply restart headscale — migrations already succeeded and are idempotent, so the version write will be retried.
Defensive patterns

Strategy: retry

Validate before calling

-- Postgres: confirm the headscale role can write metadata before deploy
-- psql as the headscale user:
SELECT has_table_privilege(current_user, 'public', 'INSERT');

Try / catch

// Safe to retry: migrations already succeeded and are idempotent.
// Restart headscale after fixing the underlying write permission; the
// version-stamping write is re-attempted on every non-dev startup.

Prevention

When it happens

Trigger: setDatabaseVersion fails: write to the version/metadata table blocked by permissions, table missing because InitSchema partially ran, connection dropped right after migrations completed, or a NOT NULL/constraint violation on the version row.

Common situations: Database user lacks UPDATE/INSERT rights on the metadata table (common in hardened postgres setups); sqlite file on a read-only mount after migrations ran from cache; a crash-restart race where the table exists but is locked.

Related errors


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