fix(lock): treat sync compromise-check I/O as a lost lock - #137
Conversation
The async interval already maps verification failures to onCompromised. The sync timer still threw from setInterval, so holders never learned the sidecar had become unreadable. Catch I/O from the sync compromise interval and fire onCompromised once. Keep explicit verifyStillHeld() throwing. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 15, 2026, 3:54 PM ET / 19:54 UTC. ClawSweeper reviewWhat this changesThe PR catches synchronous periodic sidecar-lock verification failures, reports the lock compromised once, and preserves throwing behavior for direct verification calls. Regression provenancePossible regression — suspected (reviewed change). No predecessor PR is attributed. Merge readiness✅ Ready for maintainer review Keep open for merge: current v0.5.6 still lets synchronous periodic verification throw from its timer, while this focused patch matches the existing asynchronous fail-closed behavior. The regression test and terminal proof support the change; no actionable patch defect was found. Priority: P2 Review scores
Verification
How this fits togetherSidecar locks record ownership next to a target file and periodically re-check that ownership. The synchronous monitor must notify the holder when it cannot verify the sidecar, so downstream work does not continue trusting an unverifiable lock. flowchart LR
A[Sync lock holder] --> B[Periodic sidecar check]
B --> C[Verify ownership snapshot]
C -->|held| B
C -->|changed or I/O failure| D[Stop timer]
D --> E[Call compromise callback]
E --> F[Holder handles lost lock]
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Merge the narrow parity repair so both lock-monitoring modes fail closed and notify once when periodic verification cannot read the sidecar. Do we have a high-confidence way to reproduce the issue? Yes—current main directly calls synchronous sidecar verification from the timer, and the added focused test reproduces the relevant lstatSync EIO path from source. Is this the best way to solve the issue? Yes—the patch is the narrowest maintainable fix because it mirrors the already-shipped asynchronous lost-lock policy without changing explicit verification semantics. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 01ef06f04e49. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (1 earlier review cycle)
|
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: fix(lock): treat sync compromise-check I/O as a lost lock This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
What Problem This Solves
Fixes an issue where consumers using
acquireFileLockSync()withonCompromisedwould never be told the sidecar became unreadable. The periodic check threw fromsetIntervalon I/O such asEIOorEACCES, so the holder kept working as if it still owned the lock.Why This Change Was Made
#134 already maps asynchronous compromise-check I/O to a lost lock via
.catch(() => false). The synchronous timer introduced in #59 / #64 still calledverifyStillHeld()unguarded. This PR applies the same fail-closed detector on the sync path and leaves explicitverifyStillHeld()throwing so callers that inspect on demand still see the I/O error.User Impact
A sync lock with
compromiseCheckIntervalMsnow firesonCompromisedonce when a verification read fails, then stops the timer. DirectverifyStillHeld()calls are unchanged.Evidence
terminal output from the patched package at
/tmp/fs-safe-sync-compromise. After acquire,lstatSyncon the sidecar was forced toEIO:pnpm checkpassed: 1045 tests, 61 skipped, docs examples match, pack check clean.CHANGELOG.mdupdated when release-relevantReal behavior proof
onCompromised./tmp/fs-safe-sync-compromisebuilt withpnpm build.acquireFileLockSyncwith a 15ms compromise interval, then forcedlstatSyncon the sidecar to throwEIO.onCompromisedfired once at 17ms. A later explicitverifyStillHeld()still threwEIO.EIOfrom a failing volume (the injectedEIOis the same Node error object the runtime uses).Related