mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
Concurrent track runs on the same note were corrupting the file. In a fresh workspace, four tracks fired on cron at 05:09:17Z (all failed on AI_LoadAPIKeyError, but each still wrote lastRunAt/lastRunId before the agent ran) and three more fired at 05:09:32Z. The resulting Today.md ended with stray fragments "\n>\nes-->\n-->" — tail pieces of <!--/track-target:priorities--> that a mis-aimed splice had truncated — and the priorities YAML lost its lastRunId entirely. Two compounding issues in knowledge/track/fileops.ts: 1. updateTrackBlock read the file twice: once via fetch() to resolve fenceStart/fenceEnd, and again via fs.readFile to get the bytes to splice. If another writer landed between the reads, the line indices from read #1 pointed into unrelated content in read #2, so the splice replaced the wrong range and left tag fragments behind. 2. None of the mutators (updateContent, updateTrackBlock, replaceTrackBlockYaml, deleteTrackBlock) held any lock, so concurrent read-modify-writes clobbered each other's updates. The missing lastRunId was exactly that: set by one run, overwritten by another run's stale snapshot. The fix: introduce withFileLock(absPath, fn) in knowledge/file-lock.ts, a per-path Promise-chain mutex modeled on the commitLock pattern in knowledge/version_history.ts. Callers append onto that file's chain and await — wait-queue semantics, FIFO, no timeout. The map self-cleans when a file's chain goes idle so it stays bounded across a long-running process. Wrap all four fileops mutators in it, and also wrap workspace.writeFile (which can touch the same files from the agent's tool surface and previously raced with fileops). Both callers key on the resolved absolute path so they share the same lock for the same file. Reads (fetchAll, fetch, fetchYaml) stay lock-free — fs.writeFile on files this size is atomic enough that readers see either pre- or post-state, never corruption, and stale reads are not a correctness issue for the callers that use them (scheduler, event dispatcher). The debounced version-history commit in workspace.writeFile stays outside the lock; it's deferred work that shouldn't hold up the write. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| cli | ||
| docs | ||
| experimental | ||
| python-sdk | ||
| rowboat | ||
| rowboatx | ||
| x | ||