{"record":{"id":"81267d46e8d86355","repo":"thedotmack/claude-mem","slug":"unable-to-remove-unreadable-chroma-writer-lock-at-lockpath","errorCode":null,"errorMessage":"Unable to remove unreadable Chroma writer lock at ${lockPath}: ${removeError instanceof Error ? removeError.message : String(removeError)}","messagePattern":"Unable to remove unreadable Chroma writer lock at (.+?): (.+?)","errorType":"exception","errorClass":"ChromaUnavailableError","httpStatus":null,"severity":"error","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":501,"sourceCode":"          const message = `Unable to acquire Chroma writer lock at ${lockPath}: ${error instanceof Error ? error.message : String(error)}`;\n          recordChromaVectorSearchUnavailable(message);\n          throw new ChromaUnavailableError(message, error instanceof Error ? error : undefined);\n        }\n\n        const existing = ChromaMcpManager.readChromaWriterLock(lockPath);\n        if (!existing) {\n          // Without a readable owner the liveness reaper below can never run,\n          // so vector sync would stay dead until someone deletes the file (#3916).\n          // Treat an unreadable lock that is past the write grace period as stale.\n          if (ChromaMcpManager.isChromaWriterLockAbandoned(lockPath)) {\n            try {\n              fs.rmSync(lockPath, { force: true });\n              logger.info('CHROMA_MCP', 'Removed unreadable Chroma writer lock', { lockPath });\n              continue;\n            } catch (removeError) {\n              const message = `Unable to remove unreadable 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          const message = `Chroma writer lock at ${lockPath} is unreadable; refusing to start a second writer`;\n          recordChromaVectorSearchUnavailable(message);\n          throw new ChromaUnavailableError(message);\n        }\n\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,","sourceCodeStart":483,"sourceCodeEnd":519,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/ChromaMcpManager.ts#L483-L519","documentation":"When the existing Chroma writer lock file exists but cannot be read (readChromaWriterLock returns nothing usable), the code tries to remove it (fs.rmSync force) and retry. If that removal itself fails, it cannot safely reap a stale lock or safely refuse a second writer, so it records unavailability and throws ChromaUnavailableError with the path and remove error.","triggerScenarios":"acquireChromaWriterLock finds a leftover lock file that is unreadable (wrong owner, permission bits, corrupted JSON owner info) and the rmSync cleanup throws due to permissions, immutable flag, or filesystem errors.","commonSituations":"A previous crash left a lock file owned by a different user (e.g. created under sudo or another container user); the lock file sits on a read-only mount after a container restart; NFS/overlayfs quirks making the file unreadable and unremovable.","solutions":["Manually delete the lock file at the path in the message (after confirming no chroma-mcp process is running), then retry.","Fix ownership/permissions of the lock file and its directory (chown to the running user).","Remount the volume read-write or move the data/lock directory to a writable filesystem.","Check for stale locks from previous runs as root/other users and align the user that launches the app."],"exampleFix":"// shell\npgrep -f chroma-mcp || rm -f ~/.claude-mem/chroma/writer.lock  # then restart","handlingStrategy":"try-catch","validationCode":"import fs from 'fs';\nfunction lockReadableAndRemovable(lockPath: string): boolean {\n  try {\n    fs.accessSync(lockPath, fs.constants.R_OK | fs.constants.W_OK);\n    return true;\n  } catch { return false; }\n}\nif (fs.existsSync(lockPath) && !lockReadableAndRemovable(lockPath)) {\n  logger.warn('Stale/unreadable Chroma writer lock needs manual cleanup', { lockPath });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await manager.ensureConnected();\n} catch (e) {\n  if (e instanceof ChromaUnavailableError && e.message.includes('Unable to remove unreadable Chroma writer lock')) {\n    logger.error('Remove the lock manually after verifying no writer is running', { cause: e.cause ?? e.message });\n  } else throw e;\n}","preventionTips":["Always launch the app as the same user so leftover locks remain owned/removable.","Avoid running under sudo or mixed users against the same data directory.","On container restarts, ensure the lock volume is remounted read-write.","Add cleanup of stale lock files to shutdown/startup scripts."],"tags":["chroma","filesystem","lock","permissions","stale-lock"],"backgroundTag":"permission-denied","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"}