Consolidation Lock
- Entity ID:
ent-20260410-70fca9679aed - Type:
mechanism - Scope:
shared - Status:
active
Description
The consolidation lock is a file-based mutual exclusion mechanism implemented in src/services/autoDream/consolidationLock.ts that prevents multiple Claude Code processes from running background memory consolidation (the "auto-dream" feature) simultaneously. The lock file is named .consolidate-lock and lives inside the auto-memory directory (keyed to the git root), which ensures it is writable even when the memory path comes from an env/settings override.
The lock file serves dual purposes through a clever encoding. Its mtime (modification timestamp) records when the last successful consolidation occurred -- the readLastConsolidatedAt() function simply stats the file and returns mtimeMs, costing one stat call per turn. Its body contains the PID of the process currently holding the lock. The acquisition protocol in tryAcquireConsolidationLock() implements a compare-and-swap pattern: the acquiring process writes its own PID to the file, then re-reads the file to verify it still sees its own PID. If two processes race to acquire, only the last writer's PID will be present on re-read, so the loser detects the conflict and bails out (if (parseInt(verify.trim(), 10) !== process.pid) return null). A stale-holder guard (HOLDER_STALE_MS = 60 * 60 * 1000, one hour) prevents dead processes from blocking consolidation indefinitely: if the lock's mtime is older than one hour, any process can reclaim it regardless of PID liveness.
The lock integrates into the auto-dream gate chain in autoDream.ts, which checks gates in cheapest-first order: (1) time gate -- hours since lastConsolidatedAt must exceed minHours (default 24), costing one stat; (2) session gate -- transcript count with mtime after lastConsolidatedAt must meet minSessions (default 5); (3) lock acquisition. On successful consolidation, the lock's mtime naturally advances to "now" (from the writeFile during acquire). On failure, rollbackConsolidationLock(priorMtime) rewinds the mtime to its pre-acquire value using utimes() and clears the PID body, so the failed attempt doesn't delay the next trigger. If priorMtime is 0 (no prior lock file existed), rollback unlinks the file entirely.
Key claims
clm-20260410-f1: The lock file encodes two pieces of state: mtime aslastConsolidatedAttimestamp and file body as the holding process's PID. This avoids needing a separate timestamp file. Evidence:consolidationLock.tsfile header comment -- "Lock file whose mtime IS lastConsolidatedAt. Body is the holder's PID."clm-20260410-f2: Acquisition uses a compare-and-swap protocol: write PID, re-read, verify own PID is still present. Two competing processes both write; only the last writer survives the re-read check. Evidence:consolidationLock.tstryAcquireConsolidationLock()lines 72-81 --await writeFile(path, String(process.pid))followed byif (parseInt(verify.trim(), 10) !== process.pid) return null.clm-20260410-f3: A one-hour staleness guard (HOLDER_STALE_MS = 60 * 60 * 1000) prevents dead-process PID reuse from permanently blocking consolidation. If the lock is older than one hour, it is reclaimed even if the PID appears live. Evidence:consolidationLock.tsline 19 --const HOLDER_STALE_MS = 60 * 60 * 1000with comment "Stale past this even if the PID is live (PID reuse guard)."clm-20260410-f4: On acquisition failure,rollbackConsolidationLock()rewinds the file's mtime to its pre-acquire value usingutimes()and clears the PID body, preventing a failed attempt from delaying the next consolidation trigger. If no lock existed before (priorMtime === 0), the file is deleted entirely. Evidence:consolidationLock.tsrollbackConsolidationLock()lines 96-108.clm-20260410-f5: The auto-dream system checks gates in cheapest-first order: time gate (one stat), then session scan (filtered by scan throttle of 10 minutes), then lock acquisition. This minimizes filesystem I/O on the hot path. Evidence:autoDream.tscomment lines 4-8 -- "Gate order (cheapest first): 1. Time: hours since lastConsolidatedAt >= minHours (one stat) 2. Sessions: transcript count 3. Lock."
Relations
rel-20260410-f1: ent-20260410-70fca9679aed --[implemented_in]-->src/services/autoDream/consolidationLock.tsrel-20260410-f2: ent-20260410-70fca9679aed --[consumed_by]-->src/services/autoDream/autoDream.ts(gate chain: time -> sessions -> lock)rel-20260410-f3: ent-20260410-70fca9679aed --[stored_in]--> auto-memory directory (.consolidate-lockfile, keyed to git root viagetAutoMemPath())
Sources
src-20260410-consolidation-lock-a: src/services/autoDream/consolidationLock.ts, src/services/autoDream/autoDream.ts