{"record":{"id":"661686ea379a1774","repo":"thedotmack/claude-mem","slug":"unable-to-remove-stale-chroma-writer-lock-at-lockpath","errorCode":null,"errorMessage":"Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)}","messagePattern":"Unable to remove stale Chroma writer lock at (.+?): (.+?)","errorType":"exception","errorClass":"ChromaUnavailableError","httpStatus":null,"severity":"error","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":526,"sourceCode":"\n        if (existing.pid === process.pid && existing.ownerId === this.chromaWriterOwnerId) {\n          this.chromaWriterLock = { path: lockPath, dataDir: normalizedDataDir, ownerId: this.chromaWriterOwnerId };\n          return;\n        }\n\n        if (!ChromaMcpManager.isChromaWriterLockLive(existing)) {\n          try {\n            fs.rmSync(lockPath, { force: true });\n            logger.info('CHROMA_MCP', 'Removed stale Chroma writer lock', {\n              lockPath,\n              priorPid: existing.pid,\n              priorStartedAt: existing.acquiredAt,\n            });\n            continue;\n          } catch (removeError) {\n            const message = `Unable to remove stale Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)}`;\n            recordChromaVectorSearchUnavailable(message);\n            throw new ChromaUnavailableError(message, removeError instanceof Error ? removeError : undefined);\n          }\n        }\n\n        const message = `Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer`;\n        recordChromaVectorSearchUnavailable(message);\n        throw new ChromaUnavailableError(message);\n      }\n    }\n\n    const message = `Unable to acquire Chroma writer lock at ${lockPath} after removing stale lock`;\n    recordChromaVectorSearchUnavailable(message);\n    throw new ChromaUnavailableError(message);\n  }\n\n  private releaseChromaWriterLock(): void {\n    const lock = this.chromaWriterLock;\n    if (!lock) {\n      return;","sourceCodeStart":508,"sourceCodeEnd":544,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/ChromaMcpManager.ts#L508-L544","documentation":"Thrown by acquireChromaWriterLock when a live-owner lock was deemed stale (its PID is dead) and the attempt to delete the stale lock file with fs.rmSync failed. The manager cannot safely take ownership without removing the dead owner's lock, so it marks vector search unavailable and throws ChromaUnavailableError wrapping the underlying remove error.","triggerScenarios":"EEXIST on lock write -> lock read succeeds -> isChromaWriterLockLive(existing) is false (owner PID no longer exists) -> fs.rmSync(lockPath) throws (EACCES/EPERM/EBUSY/EROFS/ENOENT edge).","commonSituations":"Lock file or parent data dir owned by another user (running as different user/sudo vs. previous session); read-only mount or immutable attribute on the lock file; antivirus/file-indexer on Windows holding the file open; filesystem permissions changed after a crash.","solutions":["Check and fix permissions on the lock file and its parent data dir so the current user can delete it (chown/chmod)","If no chroma-mcp process is running, delete the stale lock file manually and retry","On Windows, check for processes holding the lock file open (indexers, editors, antivirus) and close/reconfigure them","If the data dir is on a read-only or full volume, move/chroma data dir config to a writable location"],"exampleFix":"// before: manager cannot remove stale lock\nthrow new ChromaUnavailableError(`Unable to remove stale Chroma writer lock at ${lockPath}: ...`);\n// after: pre-flight cleanup by the caller with proper ownership\nimport fs from 'fs';\nconst lockPath = `${dataDir}/writer.lock`;\ntry { const p = JSON.parse(fs.readFileSync(lockPath,'utf-8')); if (!processExists(p.pid)) fs.unlinkSync(lockPath); } catch { /* retry via manager */ }\nawait manager.connect();","handlingStrategy":"try-catch","validationCode":"import fs from 'fs';\nfunction canRemoveStaleLock(lockPath: string): boolean {\n  try { fs.accessSync(path.dirname(lockPath), fs.constants.W_OK); return true; }\n  catch { return false; }\n}","typeGuard":"function isFileRemovalError(e: unknown): e is NodeJS.ErrnoException {\n  return e instanceof Error && ['EACCES','EPERM','EBUSY','EROFS'].includes((e as NodeJS.ErrnoException).code ?? '');\n}","tryCatchPattern":"try {\n  await manager.connect();\n} catch (e) {\n  if (e instanceof ChromaUnavailableError && /Unable to remove stale Chroma writer lock/.test(e.message)) {\n    // check FS permissions / file holders, then manual cleanup before retry\n    console.error('Fix permissions on', lockPath, 'and remove it manually if no chroma process runs');\n  } else throw e;\n}","preventionTips":["Run all Claude-mem instances as the same OS user so lock files stay deletable","Keep the chroma data dir on a local writable volume, not a read-only or network mount","Exclude the chroma data dir from antivirus/indexers that lock files on Windows","After a crash, verify lock file permissions before restarting"],"tags":["chroma","file-lock","stale-lock","file-permissions"],"backgroundTag":"file-write-failed","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}