juanfont/headscale · critical

loading users for RequestTags migration: %w

Error message

loading users for RequestTags migration: %w

What it means

Migration '202511131445' (RequestTags migration, following the forced_tags rename) fails calling ListUsers(tx, nil) while loading all users to construct a PolicyManager that validates historical --advertise-tags values. The wrapped error is a database query failure: schema/type mismatch on the users table, permission problem, or connection loss mid-migration.

Source

Thrown at hscontrol/db/db.go:622

				// Fixes: https://github.com/juanfont/headscale/issues/3006
				ID: "202601121700-migrate-hostinfo-request-tags",
				Migrate: func(tx *gorm.DB) error {
					// 1. Load policy from file or database based on configuration
					policyData, err := PolicyBytes(tx, cfg)
					if err != nil {
						log.Warn().Err(err).Msg("failed to load policy, skipping RequestTags migration (tags will be validated on node reconnect)")
						return nil
					}

					if len(policyData) == 0 {
						log.Info().Msg("no policy found, skipping RequestTags migration (tags will be validated on node reconnect)")
						return nil
					}

					// 2. Load users and nodes to create PolicyManager
					users, err := ListUsers(tx, nil)
					if err != nil {
						return fmt.Errorf("loading users for RequestTags migration: %w", err)
					}

					nodes, err := ListNodes(tx)
					if err != nil {
						return fmt.Errorf("loading nodes for RequestTags migration: %w", err)
					}

					// 3. Create PolicyManager (handles HuJSON parsing, groups, nested tags, etc.)
					polMan, err := policy.NewPolicyManager(policyData, users, nodes.ViewSlice())
					if err != nil {
						log.Warn().Err(err).Msg("failed to parse policy, skipping RequestTags migration (tags will be validated on node reconnect)")
						return nil
					}

					// 4. Process each node
					for _, node := range nodes {
						if node.Hostinfo == nil {
							continue

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Check the wrapped error - a SQL 'column does not exist' means schema drift: verify migrations table ordering and reconcile (see the forced_tags rename guidance)
  2. Ensure the migration runs with the database idle (stop other headscale/admin clients during upgrade)
  3. For flaky Postgres connectivity, fix the network/pool settings (conn max lifetime) and restart headscale so the migration transaction retries from the top
  4. Restore from backup if the schema/history mismatch cannot be reconciled
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: schema and connectivity are sane before upgrade
if err := db.Ping(); err != nil { log.Fatalf("db unreachable: %v", err) }
if _, err := db.Exec("SELECT 1 FROM users LIMIT 1"); err != nil {
	log.Fatalf("users unreadable, migration will fail: %v", err)
}

Prevention

When it happens

Trigger: The users table schema does not match what ListUsers expects (schema drift from out-of-order upgrades), the migration runs on a Postgres connection that drops, or the SELECT hits a lock timeout while another transaction holds users.

Common situations: Upgrading a database that previously ran mixed headscale versions; long-running admin transactions blocking the migration read; Postgres restart or failover during startup.

Related errors


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