gitbutlerapp/gitbutler · error

stale pre-commit index backup at '{}'; restore it to '{}' be

Error message

stale pre-commit index backup at '{}'; restore it to '{}' before retrying

What it means

Thrown by pre_commit_with_tree when a previous hook transaction left .git/index.gitbutler-hook-backup behind. The flow copies the index to that backup, swaps in the hook tree, runs hooks, then restores by atomic rename; a crash between backup and restore leaves the backup on disk and possibly the hook's temporary index in place of the user's. The guard refuses to run again until the backup is restored so no index state is lost.

Source

Thrown at crates/gitbutler-repo/src/hooks.rs:108

    // lets the restore be a single atomic rename that also keeps the permissions.
    let index_path = repo
        .index()?
        .path()
        .context("repository index has no backing file")?
        .to_owned();
    let backup_path = index_path.with_extension("gitbutler-hook-backup");
    let backup_tmp_path = index_path.with_extension("gitbutler-hook-backup.tmp");
    let mut transaction_lock =
        but_core::sync::LockFile::open(index_path.with_extension("gitbutler-hook-lock"))
            .context("failed to open pre-commit index lock")?;
    if !transaction_lock
        .try_lock()
        .context("failed to lock the index for a pre-commit hook")?
    {
        anyhow::bail!("another pre-commit hook is already using the repository index");
    }
    match std::fs::symlink_metadata(&backup_path) {
        Ok(_) => anyhow::bail!(
            "stale pre-commit index backup at '{}'; restore it to '{}' before retrying",
            backup_path.display(),
            index_path.display()
        ),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
        Err(err) => return Err(err).context("failed to inspect pre-commit index backup"),
    }
    match std::fs::remove_file(&backup_tmp_path) {
        Ok(()) => {}
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
        Err(err) => return Err(err).context("failed to remove stale temporary index backup"),
    }
    let had_index = match std::fs::copy(&index_path, &backup_tmp_path) {
        Ok(_) => {
            std::fs::rename(&backup_tmp_path, &backup_path)
                .context("failed to finalize pre-commit index backup")?;
            true
        }

View on GitHub (pinned to caf1f223d3)

Solutions

  1. With the app closed, restore the backup: mv .git/index.gitbutler-hook-backup .git/index, then verify with git status
  2. If you confirmed the current index is already correct and nothing was lost, delete the stale backup file instead
  3. Re-run the commit afterwards - the guard no longer trips
  4. Avoid killing the app or CLI mid-commit; let hook transactions finish

Example fix

# before: commit fails with 'stale pre-commit index backup at ...'
# after: restore the backed-up index, then retry the commit
mv .git/index.gitbutler-hook-backup .git/index
git status   # sanity-check the restored index
# retry the commit from the app or CLI
Defensive patterns

Strategy: validation

Validate before calling

let backup = index_path.with_extension("gitbutler-hook-backup");
if backup.exists() {
    // a previous hook run crashed mid-transaction:
    // restore (rename backup -> index) or deliberately remove it before committing again
}

Try / catch

Err(e) if e.to_string().contains("stale pre-commit index backup") => {
    // surface 'restore <backup> to <index>' to the user; never auto-delete the backup
}

Prevention

When it happens

Trigger: A previous pre-commit run was interrupted between creating .git/index.gitbutler-hook-backup and restoring it (power loss, SIGKILL of the app or CLI, hook timeout kill), and a new commit attempt enters pre_commit_with_tree - symlink_metadata(backup_path) then succeeds and the bail fires.

Common situations: Force-quitting GitButler or the but CLI during a commit with hooks; CI containers killed mid-job in a reused worktree; hook processes killed by timeouts leaving half-finished transactions.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/39c36c75a108d5a8. Report an issue: GitHub.