Background
PR #641 Round-2 R2-B4 finding identified a scope-mismatch in the project CLAUDE.md pinned rule for file_lock:
"Any code doing read-mutate-write on shared files (CLAUDE.md, journal files, ring buffer logs) MUST use this helper..."
The R2-B4 finding (now fixed in baeb2d8 via the comment edit on session_init.py:389) demonstrated that read-only access to shared files can suffer torn reads if a concurrent writer is mid-write. The session_init.py:389 case: a read of prev_session_dir could observe a torn write from a concurrent session_init invocation racing the WRITE step at L1148.
The current pin says READ-MUTATE-WRITE, implying read-only access is fine. But the R2-B4 finding empirically shows otherwise: even pure reads of shared files need lock-acquisition for consistency.
Symptom
Without lock-acquisition on reads:
# concurrent session A writes:
with file_lock(path):
new_content = compute(read(path))
atomic_write(path, new_content)
# concurrent session B reads (NO LOCK):
content = read(path) # may observe torn state mid-A-write
Atomic-rename writes mitigate this for the file-content level (POSIX atomic-rename guarantees), but the multi-step orchestration around the rename (e.g., _compute_substituted_directive reading prev state to derive new state) is NOT atomic and CAN observe inconsistent intermediate state.
Proposal
Tighten the project CLAUDE.md pin from:
"Any code doing read-mutate-write on shared files MUST use this helper..."
to:
"Any code reading OR mutating shared files concurrently with other PACT writers MUST use this helper. Read-only access still requires lock-acquisition to defend against torn reads from concurrent writers."
Affected sites to audit
After tightening the pin, audit existing code for read-only access to shared files that may need lock retrofit:
Acceptance criteria
Cross-references
Background
PR #641 Round-2 R2-B4 finding identified a scope-mismatch in the project CLAUDE.md pinned rule for
file_lock:The R2-B4 finding (now fixed in baeb2d8 via the comment edit on session_init.py:389) demonstrated that read-only access to shared files can suffer torn reads if a concurrent writer is mid-write. The session_init.py:389 case: a read of
prev_session_dircould observe a torn write from a concurrentsession_initinvocation racing the WRITE step at L1148.The current pin says READ-MUTATE-WRITE, implying read-only access is fine. But the R2-B4 finding empirically shows otherwise: even pure reads of shared files need lock-acquisition for consistency.
Symptom
Without lock-acquisition on reads:
Atomic-rename writes mitigate this for the file-content level (POSIX atomic-rename guarantees), but the multi-step orchestration around the rename (e.g.,
_compute_substituted_directivereading prev state to derive new state) is NOT atomic and CAN observe inconsistent intermediate state.Proposal
Tighten the project CLAUDE.md pin from:
to:
Affected sites to audit
After tightening the pin, audit existing code for read-only access to shared files that may need lock retrofit:
claude_md_manager.py— read paths on CLAUDE.mdsession_journal.py—read_events,read_last(already locks per pin's current scope, but verify)session_init.py— read paths on session_dir contentspact_context.py— context-file readsstaleness.py— pinned-context staleness checks (already locks, verify)failure_log.py—read_failures(already locks per pin's current scope)Acceptance criteria
file_lockpin updated to "reading OR mutating" scope.file_lock.Cross-references
file_lock(current scope: read-mutate-write only)