The ticket lock is too heavy for the loop it sits in — 607 commits, 3 locks
Measured by frankA, 2026-08-30, over the previous six hours of origin/master:
| commits | 607 |
| distinct sessions | at least 10 (a floor: only 219 of 607 carry a Claude-Session trailer) |
locks in devdocs/progress/working/ |
3 |
And the three that exist cover an A+S xtensa ticket (correctly), a PAL ticket, and
a Rust ticket. Essentially the entire night's work ran without a lock. Checking
the latest commit against every owned file: ir_codegen.inc 01:53 (no ticket in
working/), pyparser.inc 02:09 (none), cparser.inc 01:59 (none),
pasparser_call.inc 01:08 and pasparser_generic.inc 23:33 (none).
This is not a discipline problem, and treating it as one will not work
frankA's read, which I endorse: the lock is too heavy for the loop it sits in.
Agents commit every few minutes; a claim/resolve round trip per commit is
friction nobody pays voluntarily. The protocol assumes ticket-granularity work
and the contention is file-granularity at commit cadence. Those are different
shapes, and the gap is where every collision hazard lives.
The near-miss that produced this: feature-opt-o3-register-pressure was
legitimately released from working/ when its campaign parked, then the campaign
resumed without re-claiming — 8 commits, all in ir_codegen.inc. The folder
said backlog and was telling the truth about the last deliberate act. So the
ranker offered live work in the hottest shared file to every idle Track A agent,
correctly by its own rules. frankA caught it only by opening the ticket at HEAD
before claiming. A lock is a claim about the present made by an action in the past,
and nothing re-asserts it. Resuming parked work is the one transition with no
prompt to re-take the lock, because you are continuing rather than starting.
What I did NOT do
Change the protocol. That is the owner's, and it governs how every lane works.
What I did do
tools/whoholds.py — the instrument, not a rule. It answers "who has been
writing to this file and how recently" from commit history, which is current
because it is a side effect of committing rather than something anyone must
remember. It refuses to print a session count as if it were known, and states that
quiet means nothing has landed, never that nobody is in the file. That
narrows the blast radius; it does not close the question.
The fork
- Keep the lock, add a resume rule — re-claim on resuming parked work, plus
an automated nag when a
backlog/ticket accumulates commits. Cheapest; relies on the same voluntary act that is already not happening. - Lock files, not tickets — a lightweight
claim <path>with a TTL, matching the granularity contention actually has. Closest to the real shape; new machinery, and a TTL that expires mid-work is its own hazard. - Drop the lock for hot shared files and rely on
whoholds+ asking — honest about what is already happening; leaves the A/Plexer.inchazard uncovered, which is the one the letters exist for. - Make the trailer mandatory and lock nothing more — does not prevent a collision, but makes every one attributable in seconds. Strictly necessary under any of the above.
Recommendation
4 unconditionally and immediately, then 1. The trailer gap is not the cause but it makes every instance unresolvable: frankA could not identify the two agents it would most have collided with, and I could not tell it who to ask. 219 of 607 is the number to fix first because it is the cheapest and it makes options 1-3 measurable rather than argued.
Then 1, because the failure observed was a resume, not a claim anyone skipped — and a resume rule is one sentence. If collisions persist after both, 2 is the honest answer and should be built rather than exhorted.
Not 3 while lexer.inc is still shared between A and P.
DECIDED 2026-08-30 — owner, from measurement
Judgement call, made against measured outcome rather than measured exposure.
The answer
Option 4, implemented as a hook — plus option 1's resume sentence. Options 2 and 3 are REJECTED outright, and option 1's automated nag is rejected with them. Not deferred: rejected, so a future session does not re-open them from the exposure numbers.
Why — the harm did not materialise
The ticket measures exposure (how many agents were in the hot file) and reads it as risk. Measured over the same window, the realised cost:
| real (non-watcher) commits | 1262 |
| reverts | 1 |
| commits naming a clobber/collision/lost work | 1 |
| build or self-host breakage fixes | 2 |
Contention was real and concentrated — symtab.inc, ir_codegen.inc, ir.inc,
pyparser.inc, compiler.pas and builtin/pylib.pas each saw 4 distinct
sessions in one night, across 236 source files touched. Git's own merge
absorbed nearly all of it, because the sessions were editing different regions.
Context the ticket could not have: that night was a deliberate stress test — heavy parallel work to consume the weekly token budget. Owner, 2026-08-30: "last night was an excessive stress test", and from here the fleet runs slower with the coordinator choosing which tickets can go in parallel. Exposure scales down with concurrency; options 2 (file locks + TTL) and 3 (drop the lock on hot files) therefore solve a problem that is being removed by other means, and 2 would add a TTL-expires-mid-work hazard that is worse than what it replaces.
What does NOT scale down with concurrency
Attribution. Re-measured over the full night, excluding watcher tooling
(tstate* commits are a daemon with no session and are correctly untrailed):
agent commits 1262
with trailer 422 (33%)
missing 840
Two thirds of agent commits are unattributable — worse than the 219/607 the ticket cited. This is what turned frankA's near-miss into an unresolvable one: it could not identify the two sessions it would most have collided with, at any concurrency level.
Why the hook, and not more discipline
CLAUDE_CODE_SESSION_ID is present in every agent's environment. A
prepare-commit-msg hook can append the trailer unconditionally — the same
mechanism the repo already uses for .claude/hooks/no-full-suite.sh. That
fixes the gap at its cause instead of asking harder for the voluntary act that
is already failing two times in three, and it covers exactly the hurried
commits that skip it now. Filed as a Track T ticket (tooling); T owns it.
Option 1's resume rule is one sentence in CLAUDE.md and addresses the precise failure that produced this ticket — parked work resuming without re-claiming, which no reduction in concurrency prevents. Its companion nag is a new scheduled tool for a problem never observed, and is rejected.
Revisit when
Concurrency returns to stress-test levels AND a collision causes real damage — a lost change, a broken self-host, or a revert that costs more than the commit. Reopen with outcome numbers, not exposure numbers. Prior art: this decision.