Files
ersatztv/docs/decisions/records/process/parallel-session-claim.md
T
timothy b83e965994
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 28s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 20s
PR Gates / Docs update reminder (pull_request) Successful in 24s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 29s
PR Gates / decisions lifecycle (pull_request) Failing after 24s
review-verdict/h10 Exempt: docs-only change (no code, no protected path)
Review verdict / Set review-verdict status (pull_request) Successful in 7s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 26s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 25s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
PR Gates / Script tests (pytest) (pull_request) Successful in 54s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 15s
docs: make subagent use explicit, and turn "claim an issue" into a check rather than a label
Two rules that were implied but not enforceable, both demonstrated by ersatztv#649 being
implemented TWICE in parallel to completion on the same day.

Subagents. The kickoff's HARD CONSTRAINTS already require parallel disjoint slices, mandatory
independent review from a cold brief, and a named model/effort per dispatch — none of which is
satisfiable without delegation. But nothing said so outright, and a generic client preamble of the
form "do not use the Agent tool unless the user requested it" reads as a prohibition. Now stated
plainly in both CLAUDE.md (always loaded) and the kickoff (pasted per session), with what to
delegate and what to keep inline.

Claiming. `in-progress` prevents duplicate PICKUP, not duplicate WORK — the record already said so,
but step 3 told you to apply the label and nothing else. It now requires four checks first: an open
PR whose body says `fixes #N`, a remote branch naming the number, a claiming comment predating the
label (exactly what select-queue.sh's CLAIM? flag raises and deliberately leaves unresolved), and a
fresh git fetch. Each fails differently; all four are cheap.

Staleness. The second half of the #649 collision was reading origin/main once, at branch time, and
not again across hours and four review rounds. A branch on a stale base computes its diff against
that base, so `git diff origin/main` shows other sessions' merged work as DELETIONS and pushing it
reverts them. Re-fetch before every push, rebase when it moved.

process.parallel-session-claim carries the incident, including what worked: the merged
implementation was better in one respect and the discarded branch's test coverage was salvageable,
so diff the two before discarding yours.
2026-07-27 07:44:25 +02:00

3.6 KiB

key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
key title status since supersedes superseded-by rule signals mechanics
process.parallel-session-claim 2026-07-21 — Claim with `in-progress` before working; claiming is not collision safety (#542) active 2026-07-21 none none Before starting an issue, check for an existing claim four ways — open PRs referencing it, remote branches naming it, recent comments (a claim can precede the label), and a fresh `git fetch origin main` — then claim with the `in-progress` label plus a comment. A claim prevents duplicate PICKUP, not duplicate WORK. Re-fetch `origin/main` before every push, not only at branch time. parallel sessions · in-progress label · claim race · duplicate implementation · stale base · branch reverts merged work · dependency notes · shared surfaces · lore pruning · paths: `docs/handoffs/chicorytv-issue-queue.md` · issues: #542, #649, #666 `in-progress` label on the Gitea issue. The tiny read→claim race window is accepted; the later claimant backs off. Runner topology: two runners (ci-runner VM 127 + bumblebee-runner), 4 slots total.

The critical nuance is what a claim does not buy. Two sessions on non-overlapping issues can still collide in the same files. Before touching a shared surface, read the issue's dependency notes ("#234 after #231", "coordinate with #215").

When editing the standing lore/handoff doc, prune covered and stale bullets rather than appending — it is not append-only, and git keeps the history. git pull --rebase before committing it, since it is the single most contended file across parallel sessions.

The label is not the check (ersatztv#649, 2026-07-26)

#649 was implemented twice, in parallel, to completion. One session had labelled it in-progress and was three commits and four review rounds deep when a reviewer noticed origin/main had moved ten commits: the other session had already merged the same work as PR #666. The duplicate branch was discarded — pushing it would have reverted #666 and #667, showing the merged work as deletions because its diff was computed against a stale base.

Two distinct failures, both now covered by the kickoff's step 3:

  1. The claim was made, and was insufficient. The other session was presumably already underway when the label went on. A label answers "has anyone announced this?", not "is anyone doing this?" The cheap proxies for the second question are an open PR whose body says fixes #N, a remote branch with the number in it, and a claiming comment that predates the label — which is exactly the CLAIM? flag scripts/select-queue.sh already raises and deliberately does not resolve.

  2. The base went stale and nothing re-checked it. origin/main was read once, at branch time, and not again across many hours. The tell is a git diff origin/main that shows deletions you did not make. Re-fetch before every push; rebase (never merge main in) when it has moved.

Neither session did anything wrong at the moment of claiming. The lesson is that the duration of a session is the risk: the longer a branch lives, the more the "I checked at the start" evidence decays.

Worth noting what worked: the duplicate effort was not wasted. The merged implementation was better in one respect (it exports ETV_GITEA_URL as well as GITEA_BASE_URL, because pr-changed-files.sh reads the former at higher precedence), and the discarded branch's test coverage was salvaged onto the merged code as an additive tests-only PR. When you discover a collision, diff the two implementations before throwing yours away — the loser usually contains something the winner lacks.