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

  1. Retry the commit once the in-flight operation finishes - the lock is released when the other hook transaction completes
  2. Serialize commit operations per repository: one app instance or one CLI invocation at a time
  3. 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

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


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