Repository navigation
v1.12.4 - a failed folder create frees the global lock, and a tracked-file keep refusal says why
A failed folder create frees the global lock, and a tracked-file keep refusal says why
Small fixes from CodeRabbit's review of the v1.12.3 range (thread t114) and from a flaky test teardown. Nothing a user could do before is taken away, and nothing new is exposed. The installed plugin moved: scripts/lib/apply.mjs, scripts/lib/keeps.mjs, scripts/lib/repo-fs.mjs (a comment only), skills/coalwash/references/method.md and their plugin/ twins.
Fixed
- The global lock is released when the transaction folder cannot be created.
applyPlantook the global lock, then ranmkdirSyncon.claude/coalwash; a throw there (for exampleEACCES) left the lock held until its 30-minute stale window, and every other run deferred meanwhile. The create is now inside atry: on a throw the lock is released and the run returns{ ok: false, error: 'apply: cannot create the transaction folder (<code>)' }. — test:scripts/lib/apply.test.mjs,global-scope lock: a transaction folder that cannot be created refuses the run AND releases the global lock - A bin-stash failure no longer blames the lock for every cause.
recordBinItemreturnsnullfor several causes (its per-bin lock was busy, the bin's catch-all.gitignore, the bin folder, or the item or index write failed), but the flagapplyPlanraised said only that the lock could not be acquired. It now reads "recordBinItem could not record it (its per-bin lock was busy, or the catch-all .gitignore of the bin, the bin folder, or the item or index write failed)". TherecoverDanglingpartial-recovery message likewise says "refused as out-of-root or as a create whose copy could not be binned": its count (refusedOutOfRoot) also counts a create that could not be binned, and the field name is unchanged because a test pins that count. — test:scripts/lib/apply.test.mjs,fire 63: a bin-stash failure that is NOT the lock is flagged without blaming the lock - Contributor-facing: CodeQL alerts #53 to #56 (
js/file-system-race,self-ignore.test.mjs). The test read a file's inode by path and then wrote and read the same path in a loop, which the analyzer reads as a check followed by a use. The inode is now read through an open file descriptor. A single-process fixture had no second actor; the alert was a shape, not a race. The two junction tests also probe whether the volume can make a junction and skip visibly when it cannot, and the old dual-outcome directory-link test is split into two tests that each decide their branch by probing whether a rename over a directory link is refused. — test:scripts/lib/self-ignore.test.mjs - Contributor-facing: the
EBUSYteardown flake on the Windows CI leg (PR #22, job 116559157309).apply.test.mjs's ENOBUFS fail-closed test (R14 D3 bounce 2) removed its temp project while a git process still held it. Git for Windows runs a launcher,cmd\git.exe, which starts the realgit.exe; whenspawnSynckills the launcher on ENOBUFS, the real process lives on for 2.9 to 7.6 s (n = 6, measured on one box) with its working folder inside the project, and Windows will not remove a held folder. The cure is in the teardown:removeTreeinscripts/lib/test-plan.mjsretries a removal that fails withEBUSY,EPERMorENOTEMPTYwith fresh calls for up to 30 s and throws after that, so a real leak stays loud. The 32 teardown removals in seven test files go through it. The shipped engine is unchanged: its one git spawn (git --no-optional-locks ... ls-files -z) was measured to write nothing in the project, 3 of 3. One residual remains and is named: on Windows, after an ENOBUFS or timeout kill of the Git for Windows launcher, that read-onlygit ls-filescan hold the user's folder busy for a few seconds (2.9 to 7.6 s, n = 6), and it writes nothing. — test:scripts/lib/test-plan.test.mjs,removeTree removes a temp project while a child process still holds it as its cwd
Changed
recordKeepnow returns the reason it refused, in anerrorfield. The result keeps its status fields (ok,anchorDropped,anchorStored) unchanged and gainserroron a failed write; a refusal readstracked: ...orself-ignore: ...(the same prefixesapplyPlanuses), and the sandbox-folder refusal carries one too. The early guard returns (no target, a newer schema) are unchanged. The field is additive and no shipped code callsrecordKeep: its callers are tests. Thev1.12.3entry and theSECURITY.mdrecord name these refusals; untilv1.12.4the caller ofrecordKeepcould not read the reason. — test:scripts/lib/self-ignore.test.mjs, the twoL58-1keeps legsreferences/method.md§3, item 2 (and its dist twin) says what the catch path returns. It said the early guards and the catch path share the shape{ ok: false, anchorDropped: false, anchorStored: false }; the catch path now also carrieserror.- Forward note on the
[1.12.3]entry (CodeRabbitt114). That entry and theSECURITY.mdrecord citeapply.mjs:786for the planted-.gitignoreflaw. That is the line atv1.12.2, where PR #18's thread was filed (insideacquireLock's stale-takeover branch byv1.12.3); the function isensureSelfIgnore, which delegates toensureCatchAllIgnoreinrepo-fs.mjs. The released entry is not edited.