decolua/9router · warning
[DB][migrate] pre-schema backup failed (continuing): ${e.mes
Error message
[DB][migrate] pre-schema backup failed (continuing): ${e.message} What it means
runMigrationOnce in the SQLite layer takes a lightweight backup of the database before mutating the schema (when stored backupSchemaVersion < SCHEMA_VERSION and the DB is not fresh). If makeBackupDir, backupDbLite, or pruneOldBackups throws, the migration logs this warning and continues anyway — migrations proceed WITHOUT a pre-schema backup. This is deliberate fail-open behavior so a backup failure (e.g. full disk, read-only dir) doesn't block app startup.
Source
Thrown at src/lib/db/migrate.js:242
// Prune stale backups every boot so old oversized backups shrink to KEEP.
pruneOldBackups();
// Bootstrap _meta so we can read the stored backup schema version below
// (runVersionedMigrations also ensures this, but we need it earlier here).
adapter.exec(buildCreateTableSql("_meta", TABLES._meta));
// Detect a pending schema change via the central SCHEMA_VERSION const.
// A lightweight backup is taken BEFORE any schema mutation below.
const storedSchemaVer = parseInt(getMetaSync(adapter, "backupSchemaVersion", "0"), 10) || 0;
const schemaChanging = !fresh && storedSchemaVer < SCHEMA_VERSION;
if (schemaChanging) {
try {
const backupDir = makeBackupDir(`schema-${storedSchemaVer}-to-${SCHEMA_VERSION}`);
backupDbLite(adapter, backupDir);
pruneOldBackups();
console.log(`[DB][migrate] pre-schema backup ${storedSchemaVer} → ${SCHEMA_VERSION}: ${backupDir}`);
} catch (e) {
console.warn(`[DB][migrate] pre-schema backup failed (continuing): ${e.message}`);
}
}
// 1. Always run versioned migrations chain (skip-version safe)
const migInfo = runVersionedMigrations(adapter);
// 2. Additive sync (auto add missing columns/indexes declared in TABLES)
syncSchemaFromTables(adapter);
// Stamp the schema version we just reached so future boots skip re-backup.
setMetaSync(adapter, "backupSchemaVersion", SCHEMA_VERSION);
// 3. One-time legacy JSON import (only if DB was fresh on entry)
const alreadyImported = fs.existsSync(MIGRATED_MARKER);
const legacyMain = readJsonSafe(LEGACY_FILES.main);
const legacyUsage = readJsonSafe(LEGACY_FILES.usage);
const legacyDisabled = readJsonSafe(LEGACY_FILES.disabled);
const legacyDetails = readJsonSafe(LEGACY_FILES.details);View on GitHub (pinned to 90b52e06ff)
Solutions
- Check disk space and permissions on the DB directory (resolve via DATA_DIR or ~/.9router) and fix, then restart so the next schema change backs up correctly.
- Re-run the process with write permission to the data dir (chown/chmod the dir or run as the correct user).
- Manually copy the SQLite DB file before upgrading versions if automated backups keep failing.
- If migrations themselves then fail, restore from a manual snapshot; the backup warning means no safety net existed.
Example fix
// before: silently continues without backup
} catch (e) {
console.warn(`[DB][migrate] pre-schema backup failed (continuing): ${e.message}`);
}
// after: fail fast when backup is not possible in critical envs
} catch (e) {
if (process.env.STRICT_DB_BACKUP === "1") throw e;
console.warn(`[DB][migrate] pre-schema backup failed (continuing): ${e.message}`);
} Defensive patterns
Strategy: fallback
Validate before calling
// before boot, ensure the DB dir is writable
import fs from "fs";
const dir = process.env.DATA_DIR || require("os").homedir() + "/.9router";
fs.mkdirSync(dir, { recursive: true });
fs.accessSync(dir, fs.constants.W_OK); Try / catch
// wrap init; if you require backups, fail closed
try {
await initAdapter();
console.warn("[DB] started WITHOUT a verified pre-schema backup");
} catch (e) {
if (e.message.includes("backup failed")) {
console.error("Backup failed and strict mode is on — aborting");
process.exit(1);
}
throw e;
} Prevention
- Monitor free disk space on the DB volume.
- Run the app as a user that owns the data directory.
- Take a manual DB copy before upgrading app versions.
- Set a strict-mode env flag so backup failure aborts instead of continuing.
When it happens
Trigger: Booting with an existing DB whose backupSchemaVersion is lower than SCHEMA_VERSION while the backup step throws: DATA_DIR/backup directory cannot be created (EACCES/ENOENT), disk full, backupDbLite copy fails on a locked/native-SQLite handle, or pruneOldBackups hits an IO error.
Common situations: Running the server as a different user than the one that owns ~/.9router; read-only or full disk in containers; Windows file locks on the SQLite file; NUCLEAR/restricted home permissions after OS upgrade; Docker volume mounted read-only.
Related errors
- [DB] No SQLite driver available (bun/better/node/sql.js all
- ${tableName} row-count mismatch: expected ${rows.length}, go
- [DB] bun:sqlite unavailable: ${e.message}
- [DB] node:sqlite unavailable: ${e.message}
- [DB][migrate] ${tableName} row-count mismatch: expected ${ro
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/e32a2fb5c1abe76e.
Report an issue: GitHub.