nikivdev/code · error

{}. Git index stayed locked after {} retr{}; close competing

Error message

{}. Git index stayed locked after {} retr{}; close competing git/jj processes or remove stale .git/index.lock, then retry.

What it means

Raised in src/sync.rs by the `jj git export` retry wrapper when the command keeps failing because the git index is locked (.git/index.lock held by another process). After exhausting all configured retry delays (JJ_GIT_EXPORT_LOCK_RETRY_DELAYS_MS), the library bails with the jj failure message plus guidance to close competing git/jj processes or remove a stale lock, then retry. It exists to surface a persistent lock contention problem rather than looping forever.

Source

Thrown at src/sync.rs:4643

                    "jj git export hit Git index lock; retrying {retry_num}/{max_retries} in {}ms",
                    delay.as_millis()
                ),
            );
            sync_progressln!(
                "jj git export hit a transient Git index lock; retrying ({}/{}) in {}ms...",
                retry_num,
                max_retries,
                delay.as_millis()
            );
            std::thread::sleep(delay);
            continue;
        }

        jj_print_output(&output);
        let failure = jj_failure_message(&args, &output);
        if is_git_index_lock_error(&failure_text) {
            let retries = JJ_GIT_EXPORT_LOCK_RETRY_DELAYS_MS.len();
            bail!(
                "{}. Git index stayed locked after {} retr{}; close competing git/jj processes or remove stale .git/index.lock, then retry.",
                failure,
                retries,
                if retries == 1 { "y" } else { "ies" }
            );
        }
        bail!("{}", failure);
    }

    unreachable!("jj git export retry loop should always return");
}

fn jj_run_in(repo_root: &Path, args: &[&str]) -> Result<()> {
    let output = jj_run_output_in(repo_root, args)?;
    jj_print_output(&output);
    if !output.status.success() {
        bail!("{}", jj_failure_message(args, &output));
    }

View on GitHub (pinned to a747e741ae)

Solutions

  1. Close competing git/jj processes (IDE background fetches, other terminals) and re-run the sync
  2. If no process holds the lock, remove the stale lock: `rm .git/index.lock`
  3. Retry the sync operation after clearing contention
  4. Increase/inspect JJ_GIT_EXPORT_LOCK_RETRY_DELAYS_MS if contention is frequent

Example fix

// before: stale lock present
$ rm .git/index.lock
// after: retry succeeds
$ <sync command>
Defensive patterns

Strategy: retry

Validate before calling

use std::path::Path;
// before running sync, ensure no stale git index lock
let lock = Path::new(".git/index.lock");
if lock.exists() {
    eprintln!(".git/index.lock exists; close other git/jj processes or remove it before syncing");
}

Try / catch

// retry the sync after clearing the lock
for attempt in 0..3 {
    match run_sync() {
        Err(e) if e.to_string().contains("Git index stayed locked") => {
            let _ = std::fs::remove_file(".git/index.lock");
            std::thread::sleep(std::time::Duration::from_millis(500 * (attempt + 1)));
        }
        other => { other?; break; }
    }
}

Prevention

When it happens

Trigger: `jj git export` was run, its output matched is_git_index_lock_error, and every retry attempt (JJ_GIT_EXPORT_LOCK_RETRY_DELAYS_MS.len() times) still hit the index lock.

Common situations: Another git/jj process (IDE auto-fetch, git status hook, background sync) holds .git/index.lock concurrently; a crashed git process left a stale lock file; parallel tooling operating on the same repo.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/63f90df9c1e2a24f. Report an issue: GitHub.