Found by the #4 review. Poller#handle checks running? before starting a run; Supervisor#restart does not — it re-reads status == 'running' and calls start, and neither start nor Run.adopt guards either.
The reachable interleaving (narrowed but not closed by #4's reorder): a comment event pending across a tick, the poller steals the run between interrupt's row write and its Status write, sets the row running and starts a walker — then reap's restart re-reads running, calls start, and there are two walkers in one worktree.
Fix is one line — return if running?(run_id) in restart — plus a test. Reaching it needs a pending comment event surviving a tick (dispatch broke at the cap, or fail_event re-queued), so it is narrow, but the end state is the two-walker outcome half this queue exists to prevent.
Found by the #4 review.
Poller#handlechecksrunning?before starting a run;Supervisor#restartdoes not — it re-readsstatus == 'running'and callsstart, and neitherstartnorRun.adoptguards either.The reachable interleaving (narrowed but not closed by #4's reorder): a comment event pending across a tick, the poller steals the run between
interrupt's row write and its Status write, sets the row running and starts a walker — thenreap'srestartre-readsrunning, callsstart, and there are two walkers in one worktree.Fix is one line —
return if running?(run_id)inrestart— plus a test. Reaching it needs a pending comment event surviving a tick (dispatch broke at the cap, or fail_event re-queued), so it is narrow, but the end state is the two-walker outcome half this queue exists to prevent.