sinks: _write_lock stops at the process boundary — say so where it is promised#13
Merged
Conversation
… promised _DailyFileState warns that interleaved appends corrupt the rolling hash, then reassures the caller that "LocalFileSink guarantees that with _write_lock, which is why append_line does no locking of its own". It is a threading.Lock. It guarantees nothing across processes, and a caller reading that docstring to decide whether they need their own locking gets the wrong answer. emit.py already states the truth — "What is NOT guaranteed: ... two processes appending to the same log" — so the library knows. It documented the limit accurately in one file and promised the opposite in another. Two bosun workers appending to one chain produced 7 forked chains across 5825 records. Worth being precise about the harm: nothing was corrupted. 0 dangling prev_hash, 4927/4927 resolvable signatures verified. The chain branched, and verify reported CHAIN_BREAK over intact evidence — which is worse than a clean failure, because it teaches the operator to disregard the verifier. The promise now names its boundary and points at the one piece of cross-process serialisation that does exist (the flock in hook-record, Claude Code path only). Not this commit: making appends actually cross-process safe. That is a real change to the write path — an OS lock around [read head, sign, append, publish manifest] — and it lands on top of the manifest's per-record rewrite, which is its own open question. Doing both at once would re-entangle two concerns in one file. This closes the misleading half. The first draft of the guard passed against the lying docstring, because the text says "previous process wrote to it today" elsewhere and a whole-docstring search found that. It now anchors to the paragraph that makes the claim.
This was referenced Jul 17, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_DailyFileStatewarns that interleaved appends corrupt the rolling hash, then reassures the caller:It is a
threading.Lock(local_file.py:181). It guarantees nothing across processes. A caller reading the sink's own docstring to decide whether they need their own locking gets the wrong answer.emit.py:75states the truth in the same codebase — "What is NOT guaranteed: ... two processes appending to the same log" — so the library knows. It documented the limit accurately in one file and promised the opposite in another.It already fired
Two bosun workers appending to one chain produced 7 forked chains across 5825 records (forensics, 2026-07-17). All 28 fork points sit at the one node whose LLM call blocks the writer — the only interleaving window.
Worth being precise about the harm: nothing was corrupted. 0 dangling
prev_hash, 4927/4927 resolvable signatures verified. The chain branched.verifyreported CHAIN_BREAK over evidence that was in fact intact — which is worse than a clean failure, because it teaches the operator to disregard the verifier.What changes
Docstring only, plus a guard. The promise now names its boundary and points at the one piece of cross-process serialisation that does exist: the
flockin thechiplog hook-recordsubprocess, which covers the Claude Code hook path and nothing else.What this deliberately is not
Making appends actually cross-process safe. That is a real change to the write path — an OS lock around
[read head → sign → append → publish manifest]— and it lands directly on top of the manifest's per-record rewrite, which is its own open question. Doing both at once would re-entangle two concerns in one file. This closes the misleading half; the lock is worth its own decision.On the guard
The first draft passed against the lying docstring. The text says "previous process wrote to it today" further up, and a whole-docstring search for "process" found that instead of the claim. It now anchors to the paragraph that makes the promise — which is the only reason the false green surfaced at all.
Reported from the bosun migration, 2026-07-17.