abhigyanpatwari/GitNexus · warning

lockErr

Error message

lockErr

What it means

A 409 Conflict returned when acquireRepoLock fails during repo deletion. GitNexus holds a per-storage-path lock so a repo is not deleted while an analyze or embed job is reading/writing its index. The body's error field explains which operation holds the lock.

Solutions

  1. Wait for the in-flight analyze/embed job to finish, then retry the delete
  2. Poll the job status endpoint until it reports completed/failed before deleting
  3. If a stale lock persists after a crashed job, restart the GitNexus server process to release it
  4. Serialize operations: gate deletion behind completion of indexing jobs in your automation

Example fix

// before: delete while indexing
curl -X DELETE localhost:4747/api/repos/my-repo // 409 { error: lockErr }

// after: wait for the job first
const job = await res.json();
while ((await fetch(`/api/jobs/${job.id}`)).status !== 'completed') await sleep(1000);
curl -X DELETE localhost:4747/api/repos/my-repo // 200
Defensive patterns

Strategy: retry

Validate before calling

const jobs = await (await fetch('/api/jobs?repo=' + name)).json();
if (jobs.some(j => j.status === 'running')) throw new Error('job in flight; defer delete');

Try / catch

for (let i = 0; i < 5; i++) {
  const res = await fetch(`/api/repos/${name}`, { method: 'DELETE' });
  if (res.status !== 409) return res;
  await sleep(2000 * (i + 1));
}
throw new Error('repo stayed locked; an analyze/embed job may be stuck');

Prevention

When it happens

Trigger: DELETE /api/repos/:name while an analyze or embed job is actively running against the same repo's storage path.

Common situations: Typical when CI kicks off a re-index and a cleanup script deletes repos concurrently, or when a user clicks delete in the web UI while a background analyze triggered by the CLI is still running.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-15). Data as JSON: /api/errors/7ea435bf527c008d. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/server/api.ts:1236

        return;
      }
      let storagePath: string;
      try {
        storagePath = await requireDeletableStoragePath(entry);
      } catch (err: any) {
        if (err instanceof StorageDeletionError) {
          res.status(400).json({ error: err.message });
          return;
        }
        res.status(400).json({ error: err.message || 'Unsafe index storage path' });
        return;
      }

      // Acquire repo lock — prevents deleting while analyze/embed is in flight
      const lockKey = storagePath;
      const lockErr = acquireRepoLock(lockKey);
      if (lockErr) {
        res.status(409).json({ error: lockErr });
        return;
      }

      try {
        // Close any open LadybugDB handle before deleting files
        try {
          await closeLbug();
        } catch {}

        // 1. Delete the .gitnexus index/storage directory
        await fs.rm(storagePath, { recursive: true, force: true }).catch(() => {});

        // 2. Delete the cloned repo dir if it lives under ~/.gitnexus/repos/.
        // getCloneDir now throws on names that are not filesystem-safe (e.g.
        // local repos registered with names like "my project" or "org/repo").
        // Such repos legitimately have no clone dir, so treat the rejection as
        // "nothing to clean up" rather than letting it fail the delete handler.
        let cloneDir: string | null = null;

View on GitHub (pinned to ac9a4e9abd)