Files
bench/tasks/backlog/58-a-phase-card-will-not-move-while-its-work-runs.md
T
istos c256d80efc tasks: phases get a room of their own — 56, 57, 58, 59
56 takes phase members off the Board view (counts describe what is
visible; membership is the only thing hiding them, so done, archived or
an edited list gives them back). 57 draws them instead: a Phases view,
one swimlane per phase, the phase log under it, run-again where the halt
is. 58 refuses to move a phase card while a member has a live agent —
'hold the phase first' — and covers archive, which is the path that
would really have hurt. 59 sweeps merged members to done/ when the phase
merges, so an ending reads as an ending.
2026-08-02 07:15:00 +02:00

4.5 KiB

58 — A phase card will not move while its work is running

Status: Backlog Priority: Medium — the hole is narrow but what falls through it is a phase whose card and branch disagree about what is happening Type: Feature

Dragging a phase card between stages while one of its members has an agent in it is a move nobody can mean. Refuse it, name what to do — ‖ hold stops the phase and the agent it has in flight — and let the move through the moment nothing is running.

Context

  • Today a phase card is an ordinary card to move_task(). Nothing consults the phase, so a phase card can be walked back to to-do/ (or archived, which is a move) while its second member is mid-run, leaving a card in one place and a live agent, a worktree and a phase branch in another.
  • The stopping half already exists and does exactly the right thing. stop_phase() (manager/core/phases.py:662): "the agent the phase has in flight is held exactly as its own card's hold would hold it", and the branch, the merges and the worktrees are left as they were. So the fix for a refusal is one action the person already has.
  • A halted phase has nothing running by construction, so it moves freely — which is exactly when you would want to walk it back.
  • The board has almost no refusals on drag. move_task raises for a bad stage or an existing file; the drag-to-done/ sheet intercepts rather than refuses. This adds the first real "no", which is why the wording matters more than the check.

Affected areas: manager/core/taskfiles.py or manager/core/phases.py for the check, manager/core/httpd.py where /api/move and /api/archive are served, and manager/core/board.html for how a refused drag looks.

What to build

  • Refuse the move while any member has a live agent. Not "while the phase is running" — a phase between members has nothing to lose, and refusing then would be a rule people learn to resent.
  • Say the whole thing in the refusal: which member is working, and that ‖ hold stops the phase and its agent without unwinding anything. A refusal that only says no makes the person guess, and the guess is usually to force it.
  • Archiving is a move. archive_task() takes the same guard, or the rule has a hole shaped exactly like the tidiest thing to do with a phase you have lost interest in.
  • The card returns to where it was, cleanly, with the reason in the toast — a drag that snaps back with no explanation is worse than one that is not allowed to start.
  • Guard the server, not just the drag. The board can be stale, and /api/move is reachable regardless — the same lesson card 54 wrote down about ▸ start work on a phase card.

Out of scope — tempting neighbours left alone:

  • Stopping members individually. ‖ hold on the phase covers it, and a per-member stop is card 57's lane doing ordinary card things.
  • Refusing to move a member card. That is a person overriding the runner deliberately, and the phase halting on it afterwards is the honest outcome.
  • Anything about what the move does once allowed.

Acceptance

  • Given a phase with an agent running on a member, when its card is dragged to another stage, then the move is refused, the card stays, and the message names the working member and ‖ hold.
  • The same phase card cannot be archived while that agent runs.
  • Given the phase is held, the card moves — and archives — normally.
  • Given the phase has halted, the card moves normally: nothing is running.
  • Given a phase between members, with nothing launched, the card moves normally.
  • POST /api/move on that card is refused with the same reason, from a stale page or by hand.
  • An ordinary card is unaffected in every case.
  • Edge case: a member's agent that has died but not been reaped does not lock the phase card forever — the check reads what is actually running, not what was once started.

Notes

The rule earns its keep on the archive path more than the drag: a phase you have given up on is precisely the one you would archive, and doing it mid-run would leave a branch, a worktree and a running agent belonging to a card that is no longer on the board.

Worth phrasing the refusal as an instruction rather than a prohibition — "hold the phase first, then move it" reads as help, and "cannot be moved while running" reads as a wall. The board's other refusals already do this: work starts from in-progress/ — move the card there first.