{"record":{"id":"e32a2fb5c1abe76e","repo":"decolua/9router","slug":"db-migrate-pre-schema-backup-failed-continuing","errorCode":null,"errorMessage":"[DB][migrate] pre-schema backup failed (continuing): ${e.message}","messagePattern":"\\[DB\\]\\[migrate\\] pre-schema backup failed \\(continuing\\): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/lib/db/migrate.js","lineNumber":242,"sourceCode":"  // Prune stale backups every boot so old oversized backups shrink to KEEP.\n  pruneOldBackups();\n\n  // Bootstrap _meta so we can read the stored backup schema version below\n  // (runVersionedMigrations also ensures this, but we need it earlier here).\n  adapter.exec(buildCreateTableSql(\"_meta\", TABLES._meta));\n\n  // Detect a pending schema change via the central SCHEMA_VERSION const.\n  // A lightweight backup is taken BEFORE any schema mutation below.\n  const storedSchemaVer = parseInt(getMetaSync(adapter, \"backupSchemaVersion\", \"0\"), 10) || 0;\n  const schemaChanging = !fresh && storedSchemaVer < SCHEMA_VERSION;\n  if (schemaChanging) {\n    try {\n      const backupDir = makeBackupDir(`schema-${storedSchemaVer}-to-${SCHEMA_VERSION}`);\n      backupDbLite(adapter, backupDir);\n      pruneOldBackups();\n      console.log(`[DB][migrate] pre-schema backup ${storedSchemaVer} → ${SCHEMA_VERSION}: ${backupDir}`);\n    } catch (e) {\n      console.warn(`[DB][migrate] pre-schema backup failed (continuing): ${e.message}`);\n    }\n  }\n\n  // 1. Always run versioned migrations chain (skip-version safe)\n  const migInfo = runVersionedMigrations(adapter);\n\n  // 2. Additive sync (auto add missing columns/indexes declared in TABLES)\n  syncSchemaFromTables(adapter);\n\n  // Stamp the schema version we just reached so future boots skip re-backup.\n  setMetaSync(adapter, \"backupSchemaVersion\", SCHEMA_VERSION);\n\n  // 3. One-time legacy JSON import (only if DB was fresh on entry)\n  const alreadyImported = fs.existsSync(MIGRATED_MARKER);\n  const legacyMain = readJsonSafe(LEGACY_FILES.main);\n  const legacyUsage = readJsonSafe(LEGACY_FILES.usage);\n  const legacyDisabled = readJsonSafe(LEGACY_FILES.disabled);\n  const legacyDetails = readJsonSafe(LEGACY_FILES.details);","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/db/migrate.js#L224-L260","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: silently continues without backup\n} catch (e) {\n  console.warn(`[DB][migrate] pre-schema backup failed (continuing): ${e.message}`);\n}\n// after: fail fast when backup is not possible in critical envs\n} catch (e) {\n  if (process.env.STRICT_DB_BACKUP === \"1\") throw e;\n  console.warn(`[DB][migrate] pre-schema backup failed (continuing): ${e.message}`);\n}","handlingStrategy":"fallback","validationCode":"// before boot, ensure the DB dir is writable\nimport fs from \"fs\";\nconst dir = process.env.DATA_DIR || require(\"os\").homedir() + \"/.9router\";\nfs.mkdirSync(dir, { recursive: true });\nfs.accessSync(dir, fs.constants.W_OK);","typeGuard":null,"tryCatchPattern":"// wrap init; if you require backups, fail closed\ntry {\n  await initAdapter();\n  console.warn(\"[DB] started WITHOUT a verified pre-schema backup\");\n} catch (e) {\n  if (e.message.includes(\"backup failed\")) {\n    console.error(\"Backup failed and strict mode is on — aborting\");\n    process.exit(1);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["database","sqlite","migration","backup","filesystem"],"backgroundTag":"db-backup-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}