{"record":{"id":"f7646d33151edacb","repo":"BloopAI/vibe-kanban","slug":"failed-to-copy-database-file-f7646d","errorCode":null,"errorMessage":"Failed to copy database file","messagePattern":"Failed to copy database file","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/server/src/startup.rs","lineNumber":151,"sourceCode":"    shutdown: CancellationToken,\n) -> Result<DeploymentImpl, DeploymentError> {\n    // Create asset directory if it doesn't exist\n    if !asset_dir().exists() {\n        std::fs::create_dir_all(asset_dir()).map_err(|e| {\n            DeploymentError::Other(anyhow::anyhow!(\"Failed to create asset directory: {}\", e))\n        })?;\n    }\n\n    // Copy old database to new location for safe downgrades\n    let old_db = asset_dir().join(\"db.sqlite\");\n    let new_db = asset_dir().join(\"db.v2.sqlite\");\n    if !new_db.exists() && old_db.exists() {\n        tracing::info!(\n            \"Copying database to new location: {:?} -> {:?}\",\n            old_db,\n            new_db\n        );\n        std::fs::copy(&old_db, &new_db).expect(\"Failed to copy database file\");\n        tracing::info!(\"Database copy complete\");\n    }\n\n    let deployment = DeploymentImpl::new(shutdown).await?;\n    migrate_legacy_attachment_directories(&deployment).await?;\n    deployment.update_sentry_scope().await?;\n    deployment\n        .container()\n        .cleanup_orphan_executions()\n        .await\n        .map_err(DeploymentError::from)?;\n    deployment\n        .container()\n        .backfill_before_head_commits()\n        .await\n        .map_err(DeploymentError::from)?;\n    deployment\n        .container()","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/server/src/startup.rs#L133-L169","documentation":"During deployment initialization the server copies the legacy SQLite database (`db.sqlite`) to the new location (`db.v2.sqlite`) so downgrades remain safe. The copy uses `std::fs::copy(...).expect(...)` and panics on any filesystem error. This runs only when the new file does not exist yet and the old one does — a one-time migration on first start after upgrading to the v2 database layout.","triggerScenarios":"`initialize_deployment` finds `asset_dir()/db.sqlite` present and `asset_dir()/db.v2.sqlite` absent, then `std::fs::copy` fails — typically due to insufficient disk space, permission denied on the asset directory, the old file being unreadable/locked, or an I/O error during the copy.","commonSituations":"Disk-full when the database is large; asset directory owned by a different user after running the app with sudo once; read-only or sync-conflicted directory (e.g. files inside Dropbox/iCloud folders); antivirus or backup software holding the sqlite file open on Windows.","solutions":["Free disk space if the copy failed with ENOSPC, then restart","Fix permissions on the asset directory so the server user can read db.sqlite and write db.v2.sqlite (e.g. `chown -R $USER ~/.vibe-kanban` or equivalent asset dir)","Check whether db.v2.sqlite was partially created by a failed copy; delete the partial file and restart so the migration retries","Move the app data out of synced/read-only folders (iCloud Drive, Dropbox) to a normal local directory","Replace the `.expect()` with a mapped `DeploymentError` so startup fails gracefully with a clear message instead of panicking"],"exampleFix":"// before\nstd::fs::copy(&old_db, &new_db).expect(\"Failed to copy database file\");\n// after\nstd::fs::copy(&old_db, &new_db).map_err(|e| {\n    DeploymentError::Other(anyhow::anyhow!(\n        \"Failed to copy database {:?} -> {:?}: {}\", old_db, new_db, e\n    ))\n})?;","handlingStrategy":"validation","validationCode":"// Preflight the copy before letting the app do it:\nfn db_copy_will_succeed(old: &Path, new: &Path) -> bool {\n    let meta = match std::fs::metadata(old) { Ok(m) => m, Err(_) => return false };\n    let free = fs4::available_space(old.parent().unwrap()).unwrap_or(0);\n    free > meta.len() && std::fs::metadata(old.parent().unwrap()).map(|m| !m.permissions().readonly()).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"// Catch the migration panic when embedding initialize_deployment:\nlet result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    // run initialization on a thread\n}));\nif result.is_err() {\n    eprintln!(\"DB migration failed — check disk space and permissions on the asset dir, delete partial db.v2.sqlite, and retry\");\n}","preventionTips":["Ensure adequate free disk space before upgrading the app","Keep the asset/data directory out of synced or read-only folders (iCloud, Dropbox)","Run the app consistently as the same user so file ownership stays correct","Delete partially written db.v2.sqlite files after a failed migration","Patch the code to return DeploymentError instead of expect() on fs::copy"],"tags":["filesystem","sqlite","migration","panic","disk"],"backgroundTag":"filesystem-copy-failed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}