{"record":{"id":"661686ea379a1774","repo":"thedotmack/claude-mem","slug":"unable-to-remove-stale-chroma-writer-lock-at-loc","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":445,"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":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/services/sync/ChromaMcpManager.ts#L427-L463","documentation":"Thrown by ChromaMcpManager.acquireChromaWriterLock after it detected an existing writer lock whose owning PID is dead (isChromaWriterLockLive returned false) but fs.rmSync(lockPath, { force: true }) still failed to delete the stale lock file. It is surfaced as a ChromaUnavailableError (HTTP 503, code CHROMA_UNAVAILABLE) via recordChromaVectorSearchUnavailable, so semantic/vector search is treated as down. The lock is the single-writer guard over the Chroma data directory, so the manager refuses to proceed without removing it.","triggerScenarios":"Called during ensureConnected/prewarm when acquiring the writer lock at ${lockPath}; readChromaWriterLock returns a payload whose pid is no longer alive, then fs.rmSync throws (EACCES, EBUSY, read-only filesystem, antivirus/SELinux hold, or the path was removed by a race between isChromaWriterLockLive and rmSync).","commonSituations":"Running the plugin under a restricted user without delete permission on the Chroma data directory; the data dir living on a read-only or network mount (NFS/SMB) that rejects rmSync; an OS-level file lock (Windows Defender, SELinux, open file handle from a crashed process) preventing removal; two managers racing to reap the same stale lock.","solutions":["Check the lockPath in the message and inspect filesystem permissions: ensure the process user owns or has write/delete rights on the directory containing the lock file.","Look for the prior pid in the log (priorPid field) and confirm no stray chroma/uvx/python process still holds the file open; kill it if present.","Remove the stale lock file manually (rm the path from the message) once you confirm the PID is truly dead, then retry.","If on a read-only / network mount, move the Chroma data dir (CHROMA data dir / configured path) to a local writable filesystem.","Disable any antivirus/SELinux policy that locks files in the Chroma directory, or add an exclusion for the lock path."],"exampleFix":"// before: lock sits on a read-only mount\nconst lockPath = path.join(readOnlyDataDir, '.chroma-writer-lock.json');\n// after: data dir on a local writable volume\nconst lockPath = path.join(os.homedir(), '.claude-mem', 'chroma', '.chroma-writer-lock.json');","handlingStrategy":"try-catch","validationCode":"// Before starting the manager, confirm the lock dir is writable+deletable\nimport fs from 'node:fs';\nfunction canManageLock(lockPath: string): boolean {\n  try {\n    fs.accessSync(lockPath, fs.constants.W_OK);\n    // try a temp create+delete in the same dir\n    const probe = lockPath + '.probe-' + process.pid;\n    fs.writeFileSync(probe, 'x', { flag: 'wx' });\n    fs.rmSync(probe, { force: true });\n    return true;\n  } catch { return false; }\n}","typeGuard":"import { ChromaUnavailableError } from '...';\nfunction isChromaUnavailable(e: unknown): e is ChromaUnavailableError {\n  return e instanceof ChromaUnavailableError || (e instanceof Error && /stale Chroma writer lock|already owned by PID|Unable to acquire Chroma writer lock/i.test(e.message));\n}","tryCatchPattern":"try {\n  await manager.ensureConnected();\n} catch (e) {\n  if (isChromaUnavailable(e) && /remove stale Chroma writer lock/i.test(e.message)) {\n    // surface a 'vector search unavailable' state to the UI; do not crash the worker\n    markVectorSearchUnavailable(e.message);\n    return;\n  }\n  throw e;\n}","preventionTips":["Run the plugin under a user that owns the Chroma data directory and can delete files within it.","Keep the Chroma data dir on a local writable filesystem, not a read-only or network mount.","Configure antivirus/SELinux exclusions for the lock file path.","Ensure clean shutdown so releaseChromaWriterLock runs instead of leaving stale locks."],"tags":["chroma","filesystem","lock","permissions","vector-search"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}