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

  1. Restore from the backup taken before upgrading - half-rebuilt SQLite schemas are not safely repairable by hand.
  2. Free disk space (the recreation needs room for old + new tables simultaneously).
  3. Ensure no other connection holds locks, then retry once.
  4. 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

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


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