Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeszack

Relationships

#3148 A lock file read during its holder's heartbeat rewrite is treated as no lock

Opened by zack · 10/7/2026· Shipped 10/7/2026

Found while planning swamp-club#3111. Traced by reading the code; not reproduced.

What happens

FileLock's heartbeat (extend() in src/infrastructure/persistence/file_lock.ts) rewrites the lock file in place with Deno.writeTextFile every ttl/3. That call truncates the file and then writes it, so another process can read it empty or partly written. Two readers treat such a file as no lock at all:

  1. The structural drain. The scan in waitForPerModelLocks (src/cli/repo_context.ts) wraps the read and JSON.parse of each per-model lock file in a try whose catch is commented Skip unreadable lock files. A lock caught mid-rewrite is not counted as held, so a structural command such as swamp data gc can finish its drain and start work while a writer still holds that lock.
  2. inspect(). FileLock.readLockFile returns null on any read or parse error, and inspect() reports that as no lock. acquireModelLocks inspects the global lock this way, both before taking per-model locks and in its recheck after, so a writer can see a held global lock as free and write while a structural command is working.

FileLock's own acquire path already handles this: it treats a fresh unreadable file as held (a holder is mid-write) and backs off. The two readers above do not.

Impact

A narrow window per heartbeat (a truncate and a write of about 200 bytes), hit only if a reader's read lands inside it, but the result is the mutual exclusion between structural commands and writers being skipped silently.

Possible directions

  • Write the heartbeat through the existing atomic write in src/infrastructure/persistence/atomic_write.ts (temp file and rename), so a reader always sees a whole file.
  • Have the drain scan and inspect() treat a present but unreadable lock file as held, as the acquire path does.

The fix for swamp-club#3111 adds new lock-file writes and makes those atomic, but leaves the heartbeat as it is.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 7 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/7/2026, 7:52:27 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
zack assigned zack10/7/2026, 7:05:56 PM

Sign in to post a ripple.