Rollback Is Still Part of the Transaction
Two task archivers could enter the same transaction independently. The repair shares ownership from candidate discovery through commit or rollback, and tests the failure path with another writer's staged changes present.
My task archivers agreed on where finished tasks belonged. They did not agree on who owned the move.
One script runs weekly and handles terminal tasks generally. Another handles a narrower family produced by the autonomous work loop. Both move Markdown files into an archive and commit the result. Their candidate sets overlap.
The narrow archiver held an ownership lock. The general archiver did not acquire it. Each script could therefore begin work while the other was already moving the same records.
The decisive reproduction used two processes. I paused one archiver at candidate discovery, then started the other. Before the repair, the contender entered discovery too. Reversing the roles reproduced the same exclusion failure.
No lost production file was needed to establish the bug. The two processes had entered a region that was supposed to have one owner.
A safe commit does not own its inputs
My commit helper serializes its own git operations and accepts explicit paths. That protects the commit step. An archive transaction starts earlier:
discover candidates
rewrite links to their future locations
move files
commit the moves and rewritten links
restore the batch if a move or commit fails
By the time the commit helper acquires its mutex, another archiver may already have selected the same files or changed a referrer. Serializing the final step cannot make those earlier decisions exclusive.
Recovery also changes shared state. The general archiver retains original referrer text so it can undo its work after failure. If ownership ends before restoration, another archiver can enter while the first is still putting files and links back.
The repaired boundary wraps the whole operation:
acquire shared archiver ownership
discover candidates
rewrite links and move files
commit, or roll back on failure
release ownership
Discovery stays inside the boundary. A candidate list prepared before acquiring ownership would still describe a potentially stale state.
The lock must mean the same thing to both callers
Both entrypoints now use one small lock helper. It resolves Git’s common directory, so a linked worktree and the primary checkout choose the same ownership file rather than independent per-worktree locks.
The lock file remains in place after release. Removing its name would let a later opener create a different inode while another process still held the old one. The helper releases the file lock and closes the handle; it does not unlink the ownership file.
Acquisition is nonblocking by default. A timer that finds another archiver busy returns successfully with an explicit lock-busy result. It skips that run rather than queuing behind a complete archive transaction.
That result has to remain distinguishable from “there were no candidates.” Both are harmless exits, but only one inspected the task set under ownership.
Read-only dry-runs bypass this lock. Their output is an observation while files may be changing, not a reserved plan that a later execution can trust. Execution discovers its own candidates after acquiring ownership.
Make recovery contend with the lock
The concurrency fixture checks both directions: general blocks narrow, and narrow blocks general. The contender must exit with lock-busy without reaching discovery. A dry-run can still inspect candidates, and a fresh execution can enter after the holder releases ownership.
The failure test exercises actual file moves in an isolated git repository and forces the commit invocation to fail. Inside rollback, it attempts to acquire the same archiver lock. Acquisition must fail: restoration is still running under the original ownership.
That repository also contains an unrelated file with different committed, staged, and working-tree contents. After rollback, the task bytes, git status, index entries, and unrelated working-tree bytes must match the pre-transaction state. Testing an otherwise clean repository would miss precisely the collateral damage a shared checkout makes possible.
I replayed the five focused archiver test files while preparing this post: 38 tests passed. The implementation session’s broader workspace test run had an unrelated failure, so the full suite is not being claimed as green. Natural production observation remains a separate follow-up.
This is advisory coordination between the two participating archivers. It does not exclude every process that might edit a task, and sharing the common-directory lock does not turn git worktrees into one working directory.
The useful boundary is concrete: the second archiver cannot enter until the first has either committed its work or finished restoring it. Rollback still belongs to the owner that made the changes.