{"record":{"id":"4b76a6a74a64cfec","repo":"gitbutlerapp/gitbutler","slug":"there-were-already-max-attempts-backup-project-f","errorCode":null,"errorMessage":"There were already {max_attempts} backup project files - giving up","messagePattern":"There were already (.+?) backup project files - giving up","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/gitbutler-project/src/controller.rs","lineNumber":156,"sourceCode":"                    if backup_path.is_file() {\n                        continue;\n                    }\n\n                    if let Err(err) = std::fs::rename(&projects_path, &backup_path) {\n                        tracing::error!(\n                            \"Failed to rename {} to {} - application may fail to startup: {err}\",\n                            projects_path.display(),\n                            backup_path.display()\n                        );\n                    }\n\n                    bail!(\n                        \"Could not open projects file at '{}'.\\nIt was moved to {}.\\nReopen or refresh the app to start fresh.\\nError was: {probably_file_load_err}\",\n                        projects_path.display(),\n                        backup_path.display()\n                    );\n                }\n                bail!(\"There were already {max_attempts} backup project files - giving up\")\n            }\n        }\n    }\n}\n\nimpl Controller {\n    pub(crate) fn from_path(path: impl Into<PathBuf>) -> Self {\n        let path = path.into();\n        Self {\n            projects_storage: storage::Storage::from_path(&path),\n            local_data_dir: path,\n        }\n    }\n\n    pub(crate) fn add_with_best_effort<P: AsRef<Path>>(\n        &self,\n        worktree_dir: P,\n    ) -> Result<AddProjectOutcome> {","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-project/src/controller.rs#L138-L174","documentation":"Thrown by Controller::assure_app_can_startup_or_fix_it when the projects.json file fails to load and the self-healing quarantine loop finds no free backup slot. On each corrupt load the controller renames the broken file to projects.json.maybe-broken-NN (NN in 1..255) and tells the user to reopen the app to start fresh. If every one of those backup names already exists, it gives up without touching the original file, so 254+ prior recoveries accumulated without cleanup and a persistent corruption source is likely.","triggerScenarios":"Loading the project list from local_data_dir/projects.json returns an error (malformed JSON, partial write, disk fault), AND files named projects.json.maybe-broken-01 through -254 already exist in the same directory, so the 'for round in 1..max_attempts' loop never finds an unused name and falls through to the bail.","commonSituations":"Two app instances (or app + CLI) sharing one data directory and corrupting each other's writes; force-quit or crashing app leaving truncated JSON on every start; disk faults or cloud-sync tools (Dropbox/OneDrive) damaging the file; repeated recovery with nobody deleting the .maybe-broken-* leftovers; running an older app version against a newer file format.","solutions":["Close the app and archive or delete the accumulated projects.json.maybe-broken-* files in the app data directory, then reopen - the quarantine loop gets free slots and the app starts fresh","Inspect the newest .maybe-broken-* copy to recover project worktree paths and re-add the projects manually","Eliminate the root cause of repeated corruption: run only one app instance per data dir, stop cloud-syncing the data dir, and check disk health","If projects.json was hand-edited or truncated, fix the JSON in place instead of relying on the quarantine loop"],"exampleFix":"# before: recovery exhausted, startup gives up\nls \"$DATA_DIR\"/projects.json.maybe-broken-* | wc -l   # 254 files\n# after: archive old quarantines so slots free up, then relaunch\nmkdir -p ~/gb-project-backups\nmv \"$DATA_DIR\"/projects.json.maybe-broken-* ~/gb-project-backups/","handlingStrategy":"validation","validationCode":"// before Controller startup, ensure quarantine slots are free\nlet stale: Vec<_> = std::fs::read_dir(&data_dir)?\n    .filter_map(|e| e.ok())\n    .filter(|e| e.file_name().to_string_lossy().starts_with(\"projects.json.maybe-broken-\"))\n    .collect();\nif !stale.is_empty() {\n    // archive them out of the data dir before loading projects\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run one app instance per data directory","Periodically clean projects.json.maybe-broken-* leftovers","Exclude the app data dir from cloud-sync and real-time AV scanning","Avoid force-quitting the app while it writes projects.json"],"tags":["projects-file","corruption","startup","recovery","file-management"],"backgroundTag":"config-corruption-recovery-exhausted","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}