{"record":{"id":"71866c8dcc3304a0","repo":"abhigyanpatwari/GitNexus","slug":"lock-is-already-held-lockpath-confirm-no-owne","errorCode":null,"errorMessage":"Lock is already held: ${lockPath}. Confirm no owner process is active, then remove it manually.","messagePattern":"Lock is already held: (.+?)\\. Confirm no owner process is active, then remove it manually\\.","errorType":"exception","errorClass":"FileLockBusyError","httpStatus":null,"severity":"warning","filePath":"gitnexus/src/storage/file-lock.ts","lineNumber":77,"sourceCode":"\n  try {\n    for (let attempt = 0; ; attempt += 1) {\n      try {\n        await fs.link(pendingPath, resolvedPath);\n        break;\n      } catch (error) {\n        if (!(await isLockConflict(error, resolvedPath))) throw error;\n        if (\n          await reclaimStaleLock(\n            resolvedPath,\n            owner,\n            options.isProcessAlive ?? isProcessAlive,\n            options.readProcessStartTime ?? readProcessStartTime,\n          )\n        ) {\n          continue;\n        }\n        if (attempt >= retries) throw new FileLockBusyError(lockPath);\n        await sleep(retryDelayMs);\n      }\n    }\n  } finally {\n    // The lock is already published by now, but the release closure below is\n    // not yet in the caller's hands. Letting a staging-file cleanup error\n    // escape would strand a lock nobody can release, so prefer leaking the\n    // pending file — its name is per-acquisition, so it can never block anyone.\n    await fs.rm(pendingPath, { force: true }).catch(() => {});\n  }\n\n  let releasePromise: Promise<void> | undefined;\n  return () => (releasePromise ??= releaseOwnedLock(resolvedPath, owner.ownerId));\n}\n\nasync function reclaimStaleLock(\n  lockPath: string,\n  guardOwner: FileLockOwner,","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/0d1aed942f0e8b5d3bac27519fff441aceea722d/gitnexus/src/storage/file-lock.ts#L59-L95","documentation":"FileLockBusyError is thrown by acquireFileLock when the lock file at lockPath is already held (fs.link fails with EEXIST), the holder is not reclaimable as stale, and the retry budget (options.retries) is exhausted. It signals that another live process owns the lock; the message advises confirming no owner is active and removing the file manually only in that case.","triggerScenarios":"Calling acquireFileLock (directly or via acquireWatchLock, resetAutoSyncState, run, release, nextRelease) while another process holds the lock and either retries defaults to 0 or all retries are consumed; also a lock file left behind by a crashed process whose holder cannot be proven stale (different hostname, or PID alive with same start time per the check).","commonSituations":"Two GitNexus processes (CLI + watcher/server) contending for the same auto-sync lock; a previous run was SIGKILLed and the pid check cannot classify it stale (e.g. shared volume across hosts, hostname mismatch); long-held lock with retries too low.","solutions":["Wait for the owning process to finish and release the lock, then retry with a higher options.retries / retryDelayMs budget.","Inspect the lock file's owner JSON (pid, hostname, processStartTime) and verify with `ps -p <pid>` whether the owner is alive on this host.","If the owner is confirmed dead (pid not alive, or start time differs) and hostname matches, delete the lock file manually and retry.","If the lock lives on a shared volume and hostname differs from yours, coordinate with the machine that created it — do not delete blindly.","Check for orphaned processes (stray watcher/server) holding the lock and terminate them."],"exampleFix":"// before\nconst release = await acquireFileLock(lockPath);\n// after\nconst release = await acquireFileLock(lockPath, { retries: 10, retryDelayMs: 200 });","handlingStrategy":"retry","validationCode":"import fs from 'node:fs/promises';\nconst owner = JSON.parse(await fs.readFile(`${lockPath}`, 'utf8'));\nconst stale = owner.hostname === os.hostname() && !isProcessAlive(owner.pid);\nif (stale) await fs.rm(lockPath);","typeGuard":"function isFileLockBusyError(e: unknown): e is FileLockBusyError {\n  return e instanceof FileLockBusyError;\n}","tryCatchPattern":"try {\n  const release = await acquireFileLock(lockPath, { retries: 10, retryDelayMs: 200 });\n} catch (err) {\n  if (err instanceof FileLockBusyError) {\n    // inspect err.lockPath owner file; reclaim if provably stale, else surface to user\n  }\n  throw err;\n}","preventionTips":["Always configure a sensible retries/retryDelayMs budget instead of the default (0 retries).","Ensure processes release locks in a finally block so crashed-but-alive holders are rare.","Run cooperating processes on the same host, or avoid shared-volume lock paths across hosts (stale detection refuses cross-host reclaim).","Monitor for leftover .pending-* files and stale lock files and clean them in maintenance scripts after verifying the owner is dead.","Keep lock paths unique per resource to avoid unrelated contention."],"tags":["file-lock","lock-contention","busy-resource"],"backgroundTag":"file-already-exists","analyzedSha":"0d1aed942f0e8b5d3bac27519fff441aceea722d","analyzedAt":"2026-09-08T00:40:44.970Z","contentChangedAt":"2026-09-08T00:40:44.970Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}