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
- Retry the acquisition — a transient I/O blip usually clears on the next attempt.
- Move the index to a reliable local filesystem; avoid NFS/FUSE mounts for .gitnexus/ (or set GITNEXUS_STORAGE_PATH to a local dir).
- 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.
- Stop external processes (sync clients, editors, security scanners) from touching files under .gitnexus/.
- 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
- Place the index on a local disk; avoid NFS/FUSE for .gitnexus/.
- Cross-container shared mounts: set GITNEXUS_INDEX_LOCK_BACKEND=file on every writer and share one mount namespace story.
- Keep external tools (editors, scanners, sync clients) out of .gitnexus/.
- Retry transient verification failures, but never proceed lock-free.
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
- Workload-lock cleanup failed
- Guard and workload-lock cleanup failed
- Index lock acquisition timed out
- Keeping registry entry despite fs.access failure (not…
- ${message}
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)