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
- Read the wrapped error - it names the constraint/table (e.g. 'UNIQUE constraint failed: users.name').
- Restore from backup, deduplicate the offending rows manually (rename or delete duplicates) with sqlite3, then retry the upgrade.
- Find duplicates: SELECT name, COUNT(*) FROM users WHERE provider_identifier IS NULL GROUP BY name HAVING COUNT(*)>1.
- 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
- Run the duplicate-detection queries on a copy of the DB before the v0.27.0 upgrade.
- Delete stale test/duplicate users and keys regularly.
- Never re-import user rows from external dumps without checking uniqueness.
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
- creating index: %w
- foreign key constraints violated
- automigrating types.Route: %w
- automigrating types.Node: %w
- setting auth_key to null on nodes with non-existing keys: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/8af76fc1c36f05a0.
Report an issue: GitHub.