juanfont/headscale · critical
renaming table %s to %s_old: %w
Error message
renaming table %s to %s_old: %w
What it means
The SQLite schema-recreation migration failed renaming a core table to its _old variant (ALTER TABLE x RENAME TO x_old). This is the first destructive-ish step of rebuilding all tables. Failure typically means another connection holds the table, a previous failed run left an inconsistent state, or triggers/views depend on the table (legacy_sql_alter semantics).
Source
Thrown at hscontrol/db/db.go:303
}
for _, table := range tablesToRename {
// Check if table exists before renaming
var exists bool
err := tx.Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", table).Row().Scan(&exists)
if err != nil {
return fmt.Errorf("checking if table %s exists: %w", table, err)
}
if exists {
// Drop old table if it exists from previous failed migration
_ = tx.Exec("DROP TABLE IF EXISTS " + table + "_old").Error
// Rename current table to _old
err := tx.Exec("ALTER TABLE " + table + " RENAME TO " + table + "_old").Error
if err != nil {
return fmt.Errorf("renaming table %s to %s_old: %w", table, table, err)
}
}
}
// Create new tables with correct schema
tableCreationSQL := []string{
`CREATE TABLE users(
id integer PRIMARY KEY AUTOINCREMENT,
name text,
display_name text,
email text,
provider_identifier text,
provider text,
profile_pic_url text,
created_at datetime,
updated_at datetime,
deleted_at datetime
)`,View on GitHub (pinned to 565fd254d0)
Solutions
- Guarantee exclusive access: fully stop the old instance before starting the new binary.
- If a previous run crashed, restore from the pre-upgrade backup rather than hand-editing half-renamed tables.
- Check the wrapped error for 'database is locked' and identify the holder (lsof/fuser).
- Retry startup once the environment is quiet - the DROP IF EXISTS x_old cleanup makes reruns safe.
Defensive patterns
Strategy: fallback
Try / catch
if _, err := db.NewHeadscaleDatabase(cfg); err != nil {
if strings.Contains(err.Error(), "renaming table") {
// do not hand-patch half-renamed tables: restore the pre-upgrade backup and retry
}
} Prevention
- Guarantee a fresh file-level backup before the v0.27.0 upgrade (this migration rebuilds all tables).
- Ensure single-writer access; systemd 'ExecStartPre' checking no other instance holds the DB.
- Never kill headscale mid-migration; let it finish or roll back.
When it happens
Trigger: Concurrent reader on the table during RENAME; a prior aborted migration left x_old existing or half-renamed state; SQLite compiled/running with legacy_alter_table where dependent views block renames.
Common situations: Startup race where the old headscale process is still serving requests; retry after a mid-migration crash.
Related errors
- dropping routes table: %w
- setting auth_key to null on nodes with non-existing keys: %w
- adding column types.Node: %w
- creating new table: %w
- foreign key constraints violated
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/44bee7cd3cab2efd.
Report an issue: GitHub.