Repository navigation
agent: a lock file is recognized by being locked, not only by its name - #153
Merged
Merged
Conversation
The rename-target check in write_atomic was name-based, which missed: a spelling a case-insensitive filesystem folds onto a lock file (`.BEYOND-LOCK`, `LOCK`); an old binary's legacy `lock` held with no record lock file beside it; and a journal key held through Target::Itself. file_lock::is_lock_file is now: named like a record lock file (ASCII case-insensitive), or locked right now — asked of the file itself (file_lock::is_locked: F_OFD_GETLK, which also reports this process's other descriptions, plus a non-blocking test flock). A lock nobody holds needs no protecting; Cargo.lock is written as ever. Verified on a casefold ext4 directory too. The release doc now says what matters: the descriptors are closed before remove_dir (an unlink of an open file on NFS becomes a .nfs* rename until the last close). Tests (each fails with its fix reverted): held legacy lock with no record beside it, a key held Itself, and a case-folded spelling are refused by write_atomic; a hard link under any name is recognized; the folded-spelling test exercises a real case-insensitive dir when the temp dir is one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
… target write_atomic's lock-file check opened its target read-only and took a test flock on it. That hung on a FIFO (and could act on a device or block on a hung mount), made other programs' own non-blocking flocks fail (2071/20000), and refused legitimate edits of files others hold locked (a SQLite WAL database). is_lock_file now only stats. A target is a lock file when: - its name says so, ASCII case-insensitively (record lock file, or a legacy lock beside one); - its (dev, inode) is one this process holds: try_lock records each held lock file in a counted set, Drop removes them after unlocking (covers Itself keys, hard links, folded spellings of our own locks); - it is an old binary's legacy lock by where it sits: `lock` in a session dir (000001.jsonl present) or `<f>.lock` beside a *.jsonl or the MCP manifest. is_locked's probe is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
This was referenced Oct 7, 2026
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.
This PR closes the three LOW gaps from #144's final check and fixes a doc. Each fix has a test that fails without it; I checked that by reverting each fix with temporary toggles.
Revised after audit. The first version of this PR found locks by opening the target and probing it. The audit found three problems with that, all now fixed:
write_atomic. Opening a device could act, and a hung NFS mount could block.flockmade other programs' own non-blocking lock attempts fail (2071 of 20000).The probe (
is_locked) is deleted.The policy now:
file_lock::is_lock_fileonlystats. It never opens the target and never locks it. A target is a lock file only when one of these holds:Its name says so, compared without regard to ASCII case. That means a record lock file (
*.beyond-lock), or a legacylock/<f>.lockbeside one.This process holds a lock on it.
try_lockadds the (dev, inode) of each lock file it holds to a small counted set, andDropremoves them after unlocking.is_lock_filelooks up the target'sstatthere. This coversTarget::Itselfkeys and our own lock files under any name, hard link or folded spelling.It is an old binary's legacy lock with no record file beside it, judged by where it sits:
lockin a session directory (one holding000001.jsonl, which is never deleted), or<f>.lockbeside a session file (*.jsonl) or the MCP manifest.Nobody's lock is tested.
Anything else is an ordinary file, and an edit of it goes through. That includes a SQLite DB someone holds, a daemon's pid file,
Cargo.lock, and alockin a directory with no session.tools::tests::write_atomic_never_opens_its_target_to_decideflockattempts never fail while we check its filefile_lock::tests::asking_never_makes_another_programs_lock_fail(python, 20000 attempts, 0 failures)tools::tests::write_atomic_edits_a_sqlite_database_another_program_holds.BEYOND-LOCK,S2.BEYOND-LOCK, andLOCKin an old session dirfile_lock::tests::a_lock_file_is_one_by_its_name_in_any_case_or_by_where_it_sits,tools::tests::write_atomic_never_replaces_a_lock_file_a_record_beside_it_cannot_show, and…through_a_folded_spelling(run on a real casefold ext4 directory)lockin a session dir with no record beside itItselfkey, and a hard link to a held lock under any namefile_lock::tests::a_held_lock_file_is_recognized_under_any_name,tools::tests::write_atomic_never_replaces_a_lock_file_a_record_beside_it_cannot_showDoc fixes.
FileLock::release_and_remove_filesand ARCHITECTURE.md now say what actually matters: the descriptors must be closed beforeremove_dir. An NFS client turns the unlink of a file it still has open into a rename to.nfs*, which stays until the last close and makesremove_dirfail with ENOTEMPTY.The worktree seed still skips record lock files by name, now case-insensitively.
Local checks:
mcp_*,serve_*andrun_*suites plus lib units: 2392 passed.--workspace --all-targets -D warnings,cargo fmt --checkand dprint are clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk