Skip to content

council: a race says which worktree it is cutting, and ctrl+c still works - #298

Merged
sanlee-ys merged 1 commit into
mainfrom
council/arena-setup-offloop
Aug 17, 2026
Merged

council: a race says which worktree it is cutting, and ctrl+c still works#298
sanlee-ys merged 1 commit into
mainfrom
council/arena-setup-offloop

Conversation

@sanlee-ys

Copy link
Copy Markdown
Owner

/arena prepared its worktrees inside the render loop. arenaSetup ran inline in dispatch, which runs inside Update, so for as long as git took the room drew no frame and read no key. The operator measured it the hard way: parallel sessions against one repository, a lock held by another of them, and a room frozen with ctrl+c unread — the one act that could have ended the wait, sitting in a queue whose only drainer was blocked inside git worktree add.

What changed

The setup is a command. It runs on a goroutine and reports back through a channel (internal/council/arenasetup.go). dispatch stops at the point of preparing and returns; the turn is born later in applyArenaSetup, which calls the extracted sendTurn with what the setup measured. Everything the turn stamps — its clock, its context, the snapshot of the previous replies — is now stamped at the spawn rather than at the keypress, which is the honest reading of every duration it renders.

The worktree adds stay serial. That is a ruling, not an unfinished optimisation: those adds write the repository's own refs and administrative files, so N at once contend for exactly the lock this change exists to survive. TestWorktreesAreAddedOneAtATime measures it rather than assuming it — when seat N's add is announced, seat N-1's tree already exists on disk.

The frame names the step, never the progress. arena: preparing worktree for codex… in the footer, with a spinner beside it and nothing else. No percentage, no "2 of 4", no elapsed figure — council cannot measure how long a checkout takes, and a number it did not measure is one it may not draw (§4a.1). The spinner is liveness rather than progress: a step that takes a minute prints the same sentence throughout, so without a moving cell a working room and a dead one render identically, which was the old lie. TestSetupStepsNameTheWorkAndNeverTheProgress fails on any step carrying a digit or a %.

One deadline, on the setup path only. gitOutCtx is a context-carrying sibling of gitOut with exactly one caller. gitOut takes no context and can never be handed one, so every other git call council makes is un-deadlined by construction — a diff read or a commit killed by somebody's guess at a timeout is worse than a slow one. One deadline over the whole setup rather than one per call, because the number an operator experiences is how long the room was unusable.

The measurement, and the deadline it bought

Measured once on this box (Intel Mac, macOS 26.5.2, 2026-08-17): a five-seat setup against a synthetic repository built to this repo's own shape — 540 files in 60 directories, ~8 MB, against telltale's 526 tracked files and 8 MB — ran end to end, worktree adds included, in 2.3s cold and 1.3s / 1.4s warm.

The deadline is 90 seconds, ~40x that. The margin is the decision rather than the number: the failure it exists for is a lock another session holds, which is unbounded by nature and says nothing about how large the repository is. What it must never be is tight enough to kill a setup that would have finished, since a git worktree add killed mid-checkout leaves a half-created tree the operator clears by hand.

Every ending hands the room back

A deadline hit or a git refusal ends the setup wholesale — a clock is a fact about the room's patience, not about a seat, so recording it as per-seat skips would blame the vendors for one timer and then race whatever survived as if the operator had asked for a partial race. It lands on the room's existing arena notice, leading with the step before quoting git verbatim (arena: preparing worktree for codex: fatal: …): git's line names a lock, not which of eight calls met it. A process the context killed is never quoted as if git had refused it. The brief returns to the composer and the room composes again. ctrl+c stops the setup in every mode and does not quit; trees already added are kept and named, per §9.37's own rule that worktrees live until the user deletes them.

Verification

  • go vet ./... — clean.
  • go test ./internal/council -timeout 20m — ok, 42s. Also clean under -race.
  • go test ./... — every package ok.
  • One new golden, arena-setup.txt, read before acceptance; no existing golden moved.
  • The live half is owed. No race has been run against this build, so the claim that a real held index.lock now ends in a notice instead of a freeze rests on the tests and on an expired-deadline fixture, not on the lock that started this. Stated in design.md §9.37's amendment rather than implied paid.

Design record: docs/design.md §9.37, amendment dated 2026-08-17. User-facing behaviour in docs/council.md.

🤖 Generated with Claude Code

…orks

/arena prepared its worktrees inside the render loop, so for as long as git
took the room drew nothing and read no keys. Parallel sessions against one
repository put that in front of the operator: a held index.lock, and a frozen
room with ctrl+c unread — the one act that could have ended the wait, queued
behind the very command it was waiting on.

The setup runs as a command now. dispatch starts it and returns; the turn is
born later, in applyArenaSetup, through the extracted sendTurn. The per-seat
`git worktree add` calls stay serial, because those adds write the repository's
own refs and would contend for exactly the lock this change exists to survive.

While it runs the footer names the step in words — "arena: preparing worktree
for codex…" — with a spinner beside it and no percentage, no count and no
elapsed figure: council cannot measure how long a checkout takes. The spinner
is liveness, not progress; without it a working room and a dead one draw the
same sentence.

The setup carries one 90-second deadline, through gitOutCtx, a context-carrying
sibling of gitOut used by this path and nowhere else. Measured on this box: a
five-seat setup against a 540-file, 8 MB repository of telltale's own shape ran
2.3s cold and 1.3s warm, so the deadline is ~40x the measured case — sized for
a lock, which is unbounded, and never tight enough to kill a checkout that
would have finished.

Every ending hands the room back. A deadline or a git refusal stops the setup
wholesale and lands on the room's existing arena notice, leading with the step
and then quoting git verbatim; the brief returns to the composer. ctrl+c stops
the setup in every mode and does not quit. Worktrees already added are kept and
named, per this feature's own rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sanlee-ys
sanlee-ys merged commit 496bf1f into main Aug 17, 2026
5 checks passed
@sanlee-ys
sanlee-ys deleted the council/arena-setup-offloop branch August 17, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant