Repository navigation
tests/run.sh refuses to start when it cannot record its lock's owner, and its cleanup removes only a lock that is still its own (#72 items 1-2) - #232
Merged
Conversation
… and its cleanup removes only a lock that is still its own (#72 items 1-2) The `mkdir` lock (the macOS path; Linux has `flock`) names its holder by the pid inside it, and two things let that name lie. Item 1: the holder's pid write was `|| :`. A run whose pid did not get in went on into the suite holding a lock every waiter reads as stale, claims after three polls and runs beside. The pid is now read back the way a waiter reads it; where it is not there, the run releases the directory it made and exits 1 before the suite, naming the path. Item 2: the EXIT trap removed the lock's PATH. A waiter that read a live holder as gone could claim the directory and make it again with its own pid, and the old holder's exit then deleted the new holder's lock. `release_lock` now leaves a lock that carries another pid untouched (not even renamed), and removes this run's only after renaming it to a private name and reading the pid again there; a copy that turns out not to be ours is put back. The waiters' staleness rule is unchanged. Two cases in tests/test_run_wrapper.py fail before the change and pass after it, and the mkdir-lock killed-run case now also checks that no private copy is left behind. Part of #72. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR hardens the macOS mkdir lock by refusing to enter the suite unless its PID is readable and by making EXIT cleanup ownership-aware, with race-focused tests and documentation while leaving the Linux flock path unchanged. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
brettheap
marked this pull request as ready for review
October 18, 2026 01:59
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 1 day and 23 hours by commenting |
brettheap
pushed a commit
that referenced
this pull request
Oct 18, 2026
Takes in #232 (tests/run.sh records its lock owner), #233 (the openRepoShape pin at v1.1.0, 190133d) and #229 item 22 (the bench-root host-path hygiene rule). The two sides touch disjoint files, and no file this branch adds names a bench-root path. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 18, 2026
Brings in d424b64 (#201), 61a7378 (#215, the lane-start clause (c) TAIL fix), 19c2e25 (#232, tests/run.sh's lock owner), f685866 (#233, the openRepoShape pin at v1.1.0, 190133d) and f595e44 (#229 item 22, the hygiene rule on bench-root host paths) on top of 83d2690. Both sides change lanes-edit.sh and docs/README-lanes.md, in disjoint hunks: main's are `swapped`'s row spelling (Amendment 15) and its manual paragraph, the branch's are project-legs, lane-reconcile's leg and paired-root reads and the T018 section. git merged both with no conflict. bash -n passes on lanes-edit.sh, lane-handoff, lane-start, lane-end, tests/run.sh and tests/test_lane_helpers.sh. Lane: openRepoTools-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 18, 2026
…s -e, and stop counting the bash files in prose (#66, #54, #58) (#209) Landed on Brett Heap's word "land #209 #232 when green" (2026-10-18), by lane openRepoTools-5. Required Linux CI (guard-launch-mode, tests, tests-no-submodule; run 39092368132) was green on head e8c4420 with main cb3a2c7 in its tree. Main has since moved to f5c3bcd only through files this PR does not change. Independent review (lane 5, Opus, of 08dd704; the body fixed so only #66 closes; the merges of main add nothing to the PR's diff): LAND. Closes #66 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.



Part of #72: items 1 and 2,
tests/run.sh'smkdirlock (the macOS path; Linux takesflock). Both were still as filed on today's main, aa14de3.What was wrong
The
mkdirlock names its holder by the pid inside it, and two things let that name lie.Item 1: the lock was entered even when the holder could not record its pid. At aa14de3,
tests/run.sh:390read:A run whose pid did not get in (a full disk, a quota, a directory it cannot write) went on into the suite. Every waiter reads a lock with no readable pid as stale, claims it after three polls and runs beside it: two suites under one lock.
Item 2: cleanup was not ownership-safe. At aa14de3,
cleanup(the EXIT trap) ran[ -n "$LOCKDIR_HELD" ] && rm -rf -- "$LOCKDIR_HELD"(tests/run.sh:242), which removes the PATH. A waiter that reads a live holder as gone can claim the directory and make it again with its own pid inside. Two ways that happens: a pidkill -0cannot signal (another user's process), or a pid not yet written. The old holder's exit then deleted the new holder's lock, and that holder's suite ran on with no lock at all.Evidence: the two new cases below fail on aa14de3's
tests/run.sh, on exactly these two assertions.What changed
Item 1. After writing its pid, the holder reads it back the way a waiter reads it, with the same
cat. If the pid is not there, the run releases the directory it made and exits 1 before the suite starts, naming the path. The shell's own reason is no longer hidden behind2>/dev/null, so the refusal reads, for example:Item 2.
cleanupcalls a newrelease_lock, which removes the lock only while it still carries this run's pid:$LOCK.released.$$) and read again there, and only that private copy is removed. Once it is renamed, nothing another run does can change what is removed.Two instants stay open: the one between the read and the rename, and the one before a copy is put back. bash 3.2 has no rename that refuses an existing target (
mvonto a directory moves INTO it), so this is as close as it gets. The comment aboverelease_locksays so.Unchanged: the waiters' staleness rule. "A LOCK WITH NO PID IN IT IS STALE, NOT ETERNAL" and the three-poll hold still stand; only the holder side changed. The
flockpath is untouched.Docs:
AGENTS.mdanddocs/README-lanes.mdsay only that the wrapper "takes" the lock, never how it starts up or cleans up, so neither needed a change.tests/run.sh's header and its cleanup comment now state both rules.Functions and regions edited
tests/run.sh: header comment, the "flockIS LINUX-ONLY" paragraph (two sentences added)tests/run.sh: the "THE RUN ROOT AND ITS CLEANUP" comment (namesrelease_lock)tests/run.sh:release_lock(new, just abovecleanup)tests/run.sh:cleanup(its lock line)tests/run.sh: top level, the holder's pid write right after thewhile ! mkdir "$LOCKDIR"loop (the loop itself is unchanged)tests/test_run_wrapper.py: module docstring;import shlex;FAKE_SUITE(newFAKE_SUITE_TAKEOVERcue);FAKE_MKDIR(new)tests/test_run_wrapper.py:test_a_killed_run_removes_its_run_root_and_stops_the_suite(one assertion added in itsmkdir-lock branch: no private copy left)tests/test_run_wrapper.py:test_a_run_that_cannot_record_its_pid_in_the_mkdir_lock_does_not_start(new)tests/test_run_wrapper.py:test_a_holder_whose_mkdir_lock_was_taken_over_leaves_the_new_lock(new)Tests
Each case runs the wrapper on its
mkdirpath, with a private lock under the case's own tempTMPDIRand a stub suite. The workstation's real lock is never touched.test_a_run_that_cannot_record_its_pid_in_the_mkdir_lock_does_not_start. A fakemkdircalls the real one, then puts a directory where the pid file goes, so the write fails withIs a directory. That is portable, and it fails for root too, where a directory root cannot write would not stop it. The case asserts:<lock>/pid;test_a_holder_whose_mkdir_lock_was_taken_over_leaves_the_new_lock. While the wrapper holds the lock, the stub suite claims the directory the way a waiter does (mv, thenrm) and re-makes it with another live pid inside. The case asserts that the wrapper exits 0 and that the new holder's lock is still there with its pid, and nothing else.Fail before, pass after. Run inside the worktree, on these cases only (no full suite):
tests/run.sh(new tests only):2 failed, 2 passed.AssertionError: the suite started with no pid in its lock;AssertionError: the old holder's exit removed the new holder's lock;mkdir-lock killed-run cases pass; their added assertion is a guard, not a regression check.tests/run.sh:4 passed.tests/test_run_wrapper.pyplus the hygiene pintest_repo_hygiene.py::test_the_suite_wrapper_takes_one_lock_and_names_it_where_agents_read_it:15 passed in 16.94s.release_lockalone. A scratch harness took the function out oftests/run.shverbatim and drove each branch in a temp directory:mv)bash -n tests/run.shandpython3 -m py_compile tests/test_run_wrapper.pyare clean. No bash 4 constructs are used.What remains of #72 after this PR and #218
skills/handoff/SKILL.mdPR (Handoff skill: resolve lanes-edit.sh on PATH first, correct the comments that still promised write (c) without a workstation, and refuse the object-log and register-line writes for a lane that has never had a session recorded #207).Part of #72
🤖 Generated with Claude Code
Summary by Sourcery
Make the mkdir-based test lock refuse unsafe startup and preserve locks that have been taken over by another run.
Bug Fixes:
Enhancements:
Tests: