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
- With the app closed, restore the backup: mv .git/index.gitbutler-hook-backup .git/index, then verify with git status
- If you confirmed the current index is already correct and nothing was lost, delete the stale backup file instead
- Re-run the commit afterwards - the guard no longer trips
- 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
- Never SIGKILL the app or CLI during a commit
- Check for the backup file in worktree-health checks before batch commits
- Treat the backup file as the authoritative pre-hook index after a crash
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
- another pre-commit hook is already using the repository inde
- Failed to find commit {target} in rebase editor
- Failed to communicate with LM Studio server: ${error instanc
- Invalid response: ${JSON.stringify(result)}
- When using GitButler's API to summarize code, you must be lo
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/39c36c75a108d5a8.
Report an issue: GitHub.