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

  1. Reopen/refresh the app as the message says: it starts fresh with an empty projects file
  2. Inspect the error text (`Error was: ...`) to confirm whether it was a parse error, permission issue, or missing file
  3. 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)
  4. 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

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


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)