gitbutlerapp/gitbutler · critical
There were already {max_attempts} backup project files - giv
Error message
There were already {max_attempts} backup project files - giving up What it means
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.
Source
Thrown at crates/gitbutler-project/src/controller.rs:156
if backup_path.is_file() {
continue;
}
if let Err(err) = std::fs::rename(&projects_path, &backup_path) {
tracing::error!(
"Failed to rename {} to {} - application may fail to startup: {err}",
projects_path.display(),
backup_path.display()
);
}
bail!(
"Could not open projects file at '{}'.\nIt was moved to {}.\nReopen or refresh the app to start fresh.\nError was: {probably_file_load_err}",
projects_path.display(),
backup_path.display()
);
}
bail!("There were already {max_attempts} backup project files - giving up")
}
}
}
}
impl Controller {
pub(crate) fn from_path(path: impl Into<PathBuf>) -> Self {
let path = path.into();
Self {
projects_storage: storage::Storage::from_path(&path),
local_data_dir: path,
}
}
pub(crate) fn add_with_best_effort<P: AsRef<Path>>(
&self,
worktree_dir: P,
) -> Result<AddProjectOutcome> {View on GitHub (pinned to caf1f223d3)
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
Example fix
# before: recovery exhausted, startup gives up ls "$DATA_DIR"/projects.json.maybe-broken-* | wc -l # 254 files # after: archive old quarantines so slots free up, then relaunch mkdir -p ~/gb-project-backups mv "$DATA_DIR"/projects.json.maybe-broken-* ~/gb-project-backups/
Defensive patterns
Strategy: validation
Validate before calling
// before Controller startup, ensure quarantine slots are free
let stale: Vec<_> = std::fs::read_dir(&data_dir)?
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_string_lossy().starts_with("projects.json.maybe-broken-"))
.collect();
if !stale.is_empty() {
// archive them out of the data dir before loading projects
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Database file at '{db_path} has {max_attempts} corrupted cop
- Failed to rename {} to {} - application may fail to startup:
- broker already configured
- transcript contains malformed JSON before the final record
- Invalid stage '{other}' for '{rela_path}'
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/4b76a6a74a64cfec.
Report an issue: GitHub.