gitbutlerapp/gitbutler · warning
another pre-commit hook is already using the repository inde
Error message
another pre-commit hook is already using the repository index
What it means
Thrown by pre_commit_with_tree when the non-blocking lock file .git/index.gitbutler-hook-lock is already held. The hook flow swaps the git index for a temporary tree to run pre-commit hooks, so it takes an exclusive try-lock first; refusal means another commit/hook transaction is in flight right now. No state was changed when this error is returned, so a retry is safe.
Source
Thrown at crates/gitbutler-repo/src/hooks.rs:105
// Back up the index file byte for byte; a round-trip through a tree would fail
// on an index with unmerged entries (a conflict in an uncommitted file) and
// could not bring those entries back. A sibling file copy keeps memory flat and
// 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)View on GitHub (pinned to caf1f223d3)
Solutions
- Retry the commit once the in-flight operation finishes - the lock is released when the other hook transaction completes
- Serialize commit operations per repository: one app instance or one CLI invocation at a time
- If no other GitButler process is running, check for a stale .git/index.gitbutler-hook-lock left by a crashed process and remove it
Example fix
// before: concurrent commits -> "another pre-commit hook is already using the repository index"
// after: retry with backoff until the lock is free
let mut attempt = 0;
loop {
match run_pre_commit(ctx, tree_id) {
Err(e) if e.to_string().contains("another pre-commit hook") && attempt < 5 => {
attempt += 1;
std::thread::sleep(std::time::Duration::from_millis(200 * attempt));
}
r => break r,
}
} Defensive patterns
Strategy: retry
Validate before calling
let lock = index_path.with_extension("gitbutler-hook-lock");
if lock.exists() {
// another hook transaction may be in flight - wait or serialize before committing
} Try / catch
match pre_commit_with_tree(ctx, tree_id) {
Err(e) if e.to_string().contains("another pre-commit hook") => {
// transient contention: wait for the other commit to finish, then retry
}
other => other,
} Prevention
- Run a single writer (app or CLI) per repository
- Queue commits instead of firing them in parallel
- Clean up lock files after killing processes mid-commit
When it happens
Trigger: Two commit paths entering pre_commit_with_tree for the same repository concurrently - e.g. the GitButler UI committing while the 'but' CLI or an agent commits, or parallel jobs in one worktree - so LockFile::try_lock returns false.
Common situations: AI agents committing via the CLI while the desktop app auto-commits; scripts racing the app's scheduled commits; a leftover lock file from a killed process with no live holder blocking all later attempts.
Related errors
- stale pre-commit index backup at '{}'; restore it to '{}' be
- 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
- When using LM Studio, you must provide a valid endpoint
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/581f0c5818cd90a0.
Report an issue: GitHub.