juanfont/headscale · critical

copying data: %w

Error message

copying data: %w

What it means

The SQLite schema-recreation migration failed at the INSERT INTO ... SELECT * FROM <table>_old data-copy step. With new empty tables in place, copying rows back failed - most commonly a UNIQUE constraint violation (idx_api_keys_prefix, idx_provider_identifier, or the partial unique index on user names) triggered by duplicate legacy data, or type/NOT NULL violations against the stricter new schema.

Source

Thrown at hscontrol/db/db.go:408

             FROM pre_auth_keys_old`,

						`INSERT INTO api_keys (id, prefix, hash, expiration, last_seen, created_at)
             SELECT id, prefix, hash, expiration, last_seen, created_at
             FROM api_keys_old`,

						`INSERT INTO nodes (id, machine_key, node_key, disco_key, endpoints, host_info, ipv4, ipv6, hostname, given_name, user_id, register_method, forced_tags, auth_key_id, last_seen, expiry, approved_routes, created_at, updated_at, deleted_at)
             SELECT id, machine_key, node_key, disco_key, endpoints, host_info, ipv4, ipv6, hostname, given_name, user_id, register_method, forced_tags, auth_key_id, last_seen, expiry, approved_routes, created_at, updated_at, deleted_at
             FROM nodes_old`,

						`INSERT INTO policies (id, data, created_at, updated_at, deleted_at)
             SELECT id, data, created_at, updated_at, deleted_at
             FROM policies_old`,
					}

					for _, copySQL := range dataCopySQL {
						err := tx.Exec(copySQL).Error
						if err != nil {
							return fmt.Errorf("copying data: %w", err)
						}
					}

					// Create indexes
					indexes := []string{
						"CREATE INDEX idx_users_deleted_at ON users(deleted_at)",
						`CREATE UNIQUE INDEX idx_provider_identifier ON users(
  provider_identifier
) WHERE provider_identifier IS NOT NULL`,
						`CREATE UNIQUE INDEX idx_name_provider_identifier ON users(
  name,
  provider_identifier
)`,
						`CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(
  name
) WHERE provider_identifier IS NULL`,
						"CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix)",
						"CREATE INDEX idx_policies_deleted_at ON policies(deleted_at)",

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the wrapped error - it names the constraint/table (e.g. 'UNIQUE constraint failed: users.name').
  2. Restore from backup, deduplicate the offending rows manually (rename or delete duplicates) with sqlite3, then retry the upgrade.
  3. Find duplicates: SELECT name, COUNT(*) FROM users WHERE provider_identifier IS NULL GROUP BY name HAVING COUNT(*)>1.
  4. After cleanup, re-run headscale so the migration re-executes from the start.

Example fix

-- before: duplicate local users block migration
SELECT name, COUNT(*) FROM users WHERE provider_identifier IS NULL GROUP BY name HAVING COUNT(*) > 1;

-- after: rename the stale duplicate, then re-run the migration
UPDATE users SET name = name || '-old' WHERE id = <duplicate_id>;
Defensive patterns

Strategy: validation

Validate before calling

-- Pre-upgrade duplicate checks on a backup copy
SELECT name, COUNT(*) FROM users WHERE provider_identifier IS NULL GROUP BY name HAVING COUNT(*) > 1;
SELECT prefix, COUNT(*) FROM api_keys GROUP BY prefix HAVING COUNT(*) > 1;
SELECT provider_identifier, COUNT(*) FROM users WHERE provider_identifier IS NOT NULL GROUP BY provider_identifier HAVING COUNT(*) > 1;

Try / catch

if _, err := db.NewHeadscaleDatabase(cfg); err != nil {
    if strings.Contains(err.Error(), "copying data") && strings.Contains(err.Error(), "UNIQUE") {
        // restore backup, deduplicate rows named in the error, re-run the upgrade
    }
}

Prevention

When it happens

Trigger: Legacy data containing two users with the same name where provider_identifier is NULL (violates idx_name_no_provider_identifier), duplicate API key prefixes, or NULL/oversized values that the new schema rejects.

Common situations: Very old databases (pre-uniqueness-constraint era) holding duplicate usernames or key prefixes; data imported from other tools.

Related errors


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