Skip to content

Tighten file_lock pin scope: 'reading OR mutating' shared files (PR #641 R2-B4 follow-up) #649

@michael-wojcik

Description

@michael-wojcik

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:

  • claude_md_manager.py — read paths on CLAUDE.md
  • session_journal.pyread_events, read_last (already locks per pin's current scope, but verify)
  • session_init.py — read paths on session_dir contents
  • pact_context.py — context-file reads
  • staleness.py — pinned-context staleness checks (already locks, verify)
  • failure_log.pyread_failures (already locks per pin's current scope)

Acceptance criteria

  • Project CLAUDE.md file_lock pin updated to "reading OR mutating" scope.
  • Read-path audit completed across the 6 sites above; any unlocked reads either documented as safe (e.g., one-shot read at session start before concurrent writers exist) or retrofitted with file_lock.
  • Test or assertion proving torn-read defense is in place where lock retrofit is applied.

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions