fix(checkpoints): recover stale locks in diff - #68830
Conversation
Related to #13232 and #52887: this covers the same stale checkpoint index-lock family, but uses matching-error recovery, an lsof holder check, and quarantine rename for the current per-project index path. The competing safety policy needs maintainer selection rather than a duplicate designation. |
|
Thanks for the triage note. To help with the maintainer decision, here's how this PR differs from #13232 and #52887 — happy to close in favor of either if the maintainers prefer, but the recovery strategy here is meaningfully more conservative:
If the maintainers decide #13232/#52887 should land first, this PR's recovery-safety additions (lsof holder check + quarantine rename + diff() coverage) could still be layered on top as a follow-up rather than treated as a pure duplicate. Let me know which direction you'd like to take and I'll adjust. [bob] |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for targeting the real per-project index-lock failure. Current main still stages directly through git add -A in tools/checkpoint_manager.py:859-860 and :1048-1051, with GIT_INDEX_FILE bound to store/indexes/<hash> at :269-270.
Problems
- PR
tools/checkpoint_manager.py:456-471checks lsof before renaming. A live Git process can create or replace the lock after that check and beforelock_path.rename()at:465; the replacement is then moved to quarantine and unlinked. The claimed TOCTOU guarantee does not cover that interval. tests/tools/test_checkpoint_manager.py:1139-1148creates a fresh lock only during unlink, after the rename. It does not test replacement before the rename.
Suggested changes
- Use an ownership/identity protocol that proves the quarantined file is the observed stale lock before deleting it, and add a test for replacement immediately before rename.
- Exercise the real
diff()or_take()Git path with a planted stale per-project lock; the helper test currently mocks_run_git()attests/tools/test_checkpoint_manager.py:1108-1131. - Define and test behavior when lsof is unavailable; the helper intentionally declines recovery at PR
tools/checkpoint_manager.py:398-399.
Automated hermes-sweeper review.
| f"{dir_hash}.quarantine.{int(time.time_ns())}" | ||
| ) | ||
| try: | ||
| lock_path.rename(quarantine_path) |
There was a problem hiding this comment.
The lsof result was obtained before this rename. A new Git process can create or replace lock_path in that interval; this rename then moves the fresh live lock to quarantine and line 471 deletes it. Please use an ownership/identity check that proves the quarantined inode is the stale lock, and add a replacement-race test.
| lock_path.write_text("fresh\n", encoding="utf-8") | ||
| return original_unlink(self, *args, **kwargs) | ||
|
|
||
| with patch.object(Path, "unlink", create_new_original_before_unlink): |
There was a problem hiding this comment.
This test creates the fresh lock during quarantine unlink, after lock_path.rename() has completed. Add coverage that replaces the lock immediately before rename; that is the unsafe interval between the lsof check and the rename.
Summary
Test Plan