juanfont/headscale · critical
creating new table: %w
Error message
creating new table: %w
What it means
The SQLite schema-recreation migration failed executing one of the hardcoded CREATE TABLE statements (users, pre_auth_keys, api_keys, nodes, policies) that define the v0.27.0 canonical schema. Since old tables were renamed to _old just before, a failure here means the new empty table could not be created: name collision from a prior failed run, locked database, or a leftover object with the same name.
Source
Thrown at hscontrol/db/db.go:378
created_at datetime,
updated_at datetime,
deleted_at datetime,
CONSTRAINT fk_nodes_user FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_nodes_auth_key FOREIGN KEY(auth_key_id) REFERENCES pre_auth_keys(id)
)`,
`CREATE TABLE policies(
id integer PRIMARY KEY AUTOINCREMENT,
data text,
created_at datetime,
updated_at datetime,
deleted_at datetime
)`,
}
for _, createSQL := range tableCreationSQL {
err := tx.Exec(createSQL).Error
if err != nil {
return fmt.Errorf("creating new table: %w", err)
}
}
// Copy data directly using SQL
dataCopySQL := []string{
`INSERT INTO users (id, name, display_name, email, provider_identifier, provider, profile_pic_url, created_at, updated_at, deleted_at)
SELECT id, name, display_name, email, provider_identifier, provider, profile_pic_url, created_at, updated_at, deleted_at
FROM users_old`,
`INSERT INTO pre_auth_keys (id, key, user_id, reusable, ephemeral, used, tags, expiration, created_at)
SELECT id, key, user_id, reusable, ephemeral, used, tags, expiration, created_at
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)View on GitHub (pinned to 565fd254d0)
Solutions
- Restore from the backup taken before upgrading - half-rebuilt SQLite schemas are not safely repairable by hand.
- Free disk space (the recreation needs room for old + new tables simultaneously).
- Ensure no other connection holds locks, then retry once.
- If retrying is unavoidable, verify via sqlite_master that no duplicate partially-created objects exist first.
Defensive patterns
Strategy: fallback
Try / catch
if _, err := db.NewHeadscaleDatabase(cfg); err != nil {
if strings.Contains(err.Error(), "creating new table") {
// inconsistent state after a crashed run: restore backup rather than manual DDL
}
} Prevention
- Keep the pre-upgrade backup until the server has started successfully at least once.
- Ensure disk space for old + new copies of all tables during the rebuild.
- After any failed attempt, restore the backup instead of retrying against a half-rebuilt schema.
When it happens
Trigger: CREATE TABLE rejected because a table/view/index with that name still exists (inconsistent state from an aborted earlier attempt), the database is locked, or the file is read-only/full.
Common situations: Retrying the migration after a crash where the transaction rollback did not fully restore names; disk full during the rebuild.
Related errors
- adding column types.Node: %w
- dropping routes table: %w
- renaming table %s to %s_old: %w
- foreign key constraints violated
- automigrating types.Route: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/8a8a18bb177e2ce1.
Report an issue: GitHub.