abhigyanpatwari/GitNexus · error · Error

Index lock verification failed

Error message

Index lock verification failed: ${lockPath}

What it means

After creating analyze.lock via O_EXCL and writing its JSON ownership record, acquireViaFile re-reads the file and asserts the record still carries this attempt's token. If it does not, the write did not durably land or something replaced the file between write and read — the lock would be unverifiable ownership, so the acquisition is refused (the error is then wrapped/rolled back by the caller's cleanup paths). This is a self-check invariant, not a contention signal.

Solutions

  1. Retry the acquisition — a transient I/O blip usually clears on the next attempt.
  2. Move the index to a reliable local filesystem; avoid NFS/FUSE mounts for .gitnexus/ (or set GITNEXUS_STORAGE_PATH to a local dir).
  3. If you share a bind-mounted index across containers, set GITNEXUS_INDEX_LOCK_BACKEND=file and ensure all writers share the same mount and mount namespace semantics.
  4. Stop external processes (sync clients, editors, security scanners) from touching files under .gitnexus/.
  5. Check dmesg/filesystem health for underlying I/O errors; run analyze again after a clean state (no writers, per RUNBOOK.md).

Example fix

// before (index on NFS)
// repo on ~/net/repo, .gitnexus/ served over NFS

// after (local storage path)
$ GITNEXUS_STORAGE_PATH=~/.local/share/gitnexus/repo npx gitnexus analyze
Defensive patterns

Strategy: fallback

Validate before calling

// Prefer reliable local storage; detect risky mounts up front
import { statfsSync } from 'node:fs';
const fsType = statfsSync('.gitnexus').type; // e.g. 'nfs', 'fuse.*'
if (/^(nfs|cifs|fuse)/.test(String(fsType))) {
  console.warn('index on a network/FUSE filesystem — use GITNEXUS_STORAGE_PATH on a local disk');
}

Try / catch

try {
  const handle = await acquireIndexLock(lockDir);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Index lock verification failed')) {
    // unverified ownership — retry once, then fail closed (never write without the lock)
    await sleep(1000);
    return acquireIndexLock(lockDir);
  }
  throw err;
}

Prevention

When it happens

Trigger: writeSync to the freshly created lock fd succeeded but a subsequent readRecord(lockPath) returns a record whose token differs (file truncated, replaced, or written concurrently by another writer), or readRecord returns null because the content is malformed/empty.

Common situations: Unreliable filesystem semantics (NFS, some FUSE/network mounts) where O_EXCL create+write isn't atomic across clients; two processes sharing a mount in separate network namespaces bypassing the socket lock (the documented cross-netns caveat); disk-full or I/O errors silently corrupting the record; external tools touching .gitnexus/ files.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at gitnexus/src/storage/index-lock.ts:632

            } else {
              holder = current;
            }
          } else if (code === 'EPERM') {
            throw err;
          } else if (isLockUnwritableCode(code)) {
            return deniedCreateHandle(lockPath, me, err);
          } else {
            throw err;
          }
        }
        if (createdMain && fd !== undefined) {
          try {
            writeSync(fd, JSON.stringify(me));
          } finally {
            closeSync(fd);
          }
          if (readRecord(lockPath)?.token !== me.token) {
            throw new Error(`Index lock verification failed: ${lockPath}`);
          }
          let released = false;
          return {
            record: me,
            release: () => {
              if (released) return;
              released = true;
              // No async boundary: a cooperating contender cannot replace a live
              // owner's record before this one-shot unlink. Unknown is not ours.
              try {
                if (readRecord(lockPath)?.token !== me.token) return;
                unlinkSync(lockPath);
              } catch {
                /* best-effort on exit; never retry against a successor */
              }
            },
          };
        }

View on GitHub (pinned to ac9a4e9abd)