{"record":{"id":"22de02662aacb98b","repo":"thedotmack/claude-mem","slug":"chroma-data-dir-normalizeddatadir-is-already-owned-by-pid","errorCode":null,"errorMessage":"Chroma data dir ${normalizedDataDir} is already owned by PID ${existing.pid}; refusing to start a second writer","messagePattern":"Chroma data dir (.+?) is already owned by PID (.+?); refusing to start a second writer","errorType":"exception","errorClass":"ChromaUnavailableError","httpStatus":null,"severity":"error","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":532,"sourceCode":"        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;\n    }\n    this.chromaWriterLock = null;\n\n    const existing = ChromaMcpManager.readChromaWriterLock(lock.path);\n    if (!existing) {\n      logger.debug('CHROMA_MCP', 'Chroma writer lock already missing or unreadable during release', {","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/ChromaMcpManager.ts#L514-L550","documentation":"Thrown by acquireChromaWriterLock when the lock file exists, is readable, and its recorded owner PID is still alive but belongs to a different process (and a different ownerId), meaning another running process already owns the Chroma data dir. Starting a second persistent chroma-mcp writer against the same data dir would corrupt it, so the manager refuses, marks vector search unavailable, and throws ChromaUnavailableError.","triggerScenarios":"Two Claude-mem instances (e.g., two editor windows, a CLI plus an IDE, or a leaked chroma-mcp child from a prior session) both call connectInternal with the same chroma data dir; the first holds the live writer lock and the second sees EEXIST -> live PID != own PID.","commonSituations":"Duplicate Claude-mem plugin instances configured with the same CHROMA data dir; a leftover chroma-mcp prewarm child that outlived its parent; running dev build and installed build concurrently; shared data dir across multiple machines on a network drive.","solutions":["Find the owning process (PID is in the message) and stop it: kill <pid> or close the other Claude-mem/editor instance","If the PID shown is actually dead or wrong (recycled PID), delete the lock file and retry - the liveness check misread the process","Configure different chroma data dirs per concurrent instance so each has a single writer","Ensure only one Claude-mem plugin instance/entry point is active at a time (disable duplicates in the marketplace config)"],"exampleFix":"// before: two managers share one data dir\nconst shared = '~/.claude-mem/chroma';\nconst a = new ChromaMcpManager(shared); await a.connect();\nconst b = new ChromaMcpManager(shared); await b.connect(); // throws\n// after: instance-local data dir or reuse one manager\nconst mgr = new ChromaMcpManager(shared);\nawait mgr.connect(); // single writer\n// or per-instance:\nconst dir = path.join(os.tmpdir(), `claude-mem-chroma-${process.pid}`);","handlingStrategy":"try-catch","validationCode":"import fs from 'fs';\nfunction chromaDataDirIsOwned(dataDir: string): number | null {\n  try {\n    const p = JSON.parse(fs.readFileSync(`${dataDir}/writer.lock`, 'utf-8'));\n    try { process.kill(p.pid, 0); return p.pid; } catch { return null; }\n  } catch { return null; }\n}","typeGuard":"function isChromaUnavailableError(e: unknown): e is ChromaUnavailableError {\n  return e instanceof ChromaUnavailableError;\n}","tryCatchPattern":"try {\n  await manager.connect();\n} catch (e) {\n  if (e instanceof ChromaUnavailableError && /already owned by PID/.test(e.message)) {\n    const pid = Number(/PID (\\d+)/.exec(e.message)?.[1]);\n    // stop the duplicate instance or reuse the existing manager instead of starting a second writer\n  } else throw e;\n}","preventionTips":["Run exactly one Claude-mem instance per chroma data dir - disable duplicate plugin copies (dev vs installed)","Reuse a single ChromaMcpManager/singleton instead of constructing new ones per call site","Point concurrent instances at separate data dirs","Check for orphaned chroma-mcp children after abnormal exits (ps aux | grep chroma-mcp)"],"tags":["chroma","file-lock","already-running","single-writer"],"backgroundTag":"address-already-in-use","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"}