gitbutlerapp/gitbutler · error
Could not open projects file at
Error message
Could not open projects file at '{}'.\nIt was moved to {}.\nReopen or refresh the app to start fresh.\nError was: {probably_file_load_err} What it means
Thrown by `assure_app_can_startup_or_fix_it` when the projects database file (`projects.json`) cannot be loaded, and the self-healing step that moves the unreadable file to a backup path has completed. The app cannot start without a valid projects store, so it bails with a message telling the user where the file was moved and that restarting will create a fresh one.
Solutions
- Reopen/refresh the app as the message says: it starts fresh with an empty projects file
- Inspect the error text (`Error was: ...`) to confirm whether it was a parse error, permission issue, or missing file
- Recover data from the backup file at the path shown in the message (fix its JSON and copy it back as `projects.json` before restarting)
- Check disk space and file permissions in the app data directory if the problem recurs
Example fix
// corrupted projects.json
// before: {"projects": [ {"id": 1, "path": "C:\\repo" <- truncated
// after: restore a valid backup over projects.json before launching:
// {"projects": [{"id": 1, "path": "/home/user/repo"}]}
// or simply restart to get a fresh empty file Defensive patterns
Strategy: try-catch
Validate before calling
// pre-validate the projects file before app startup
let raw = fs::read_to_string(&projects_path)?;
if serde_json::from_str::<serde_json::Value>(&raw).is_err() {
fs::rename(&projects_path, &backup_path)?; // quarantine, start fresh
} Try / catch
match load_projects(&projects_path) {
Err(err) => {
eprintln!("Could not open projects file: {err}; moved to backup, starting fresh");
Projects::default()
}
Ok(p) => p,
} Prevention
- Do not hand-edit projects.json while the app is running
- Keep free disk space in the app data directory
- Back up the app data directory before upgrading GitButler
- Fix JSON syntax in the backup file and restore it if project list data matters
When it happens
Trigger: Startup calls `assure_app_can_startup_or_fix_it`; `projects.json` fails to parse/load (`probably_file_load_err`), the rename to a backup path succeeds, and this bail reports the moved location and original error.
Common situations: Projects file corrupted by a crash mid-write or full disk; hand-edited JSON with syntax errors; permissions problems after migrating machines; schema changes after a GitButler version upgrade.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- failed to create app settings
- failed to create config dir
- product name not set
- broker already configured
- Cannot pass both --local and --global
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/bdc4b51fa125fe63.
Report an issue: GitHub.
Appendix: source
Thrown at crates/gitbutler-project/src/controller.rs:150
let projects_path = self.local_data_dir.join("projects.json");
let max_attempts = 255;
for round in 1..max_attempts {
let backup_path = self
.local_data_dir
.join(format!("projects.json.maybe-broken-{round:02}"));
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,
}View on GitHub (pinned to 58e5313667)