feat(worker): Handler-Session nach Usage-Limit-Reset automatisch neu starten
Eine List-Handler-ConPTY-Session, die mitten in einer Phase stirbt, war bisher
ein stilles totes Tile: InteractiveTerminalViewModel.OnSessionProcessExited
setzte nur bei einem Tod innerhalb des 5s-Grace-Fensters ein sichtbares Banner.
Neu:
- HandlerRoundState (JSON unter ~/.claudeDo/handler-rounds.json, keine
EF-Migration): Phase + Task-Set der laufenden Runde, geschrieben von
BuildForMergeHelperAsync ("triage") und BuildForMergeHelperHandoffAsync.
Ohne das existieren beide nur im transienten HandoffRequested-Broadcast.
- HandlerRestartCoordinator: entscheidet den Neustart, fail-closed. Beleg fuer
"war das Usage-Limit" ist FiveHour.Utilization >= 95 zum Todeszeitpunkt --
eine ConPTY-Session hat kein stdout, TaskRunner.IsUsageLimitMessage hat hier
nichts zu lesen. Nur bei PowerUse (beide Keys), max ein Neustart pro Runde,
Warten bis FiveHour.ResetsAt, dann HandoffRequested erneut broadcasten.
Verbraucht kein Handoff-Budget (HandoffRoundTracker.Register wird nicht
angefasst). WorkerLog-Zeilen beim Tod und beim Neustart.
- Ui meldet den Exit-Code (nur dort bekannt) via
WorkerHub.ReportInteractiveSessionExited, ohne selbst zu filtern.
Sibling zu UsageLimitAutoContinueCoordinator, kein Umbau davon: der sieht nur
headless Runs.
This commit is contained in:
@@ -143,6 +143,43 @@ outgoing session is only ever told to end its turn, never to exit. This keeps th
|
||||
`MCP_TOOL_TIMEOUT` is 200 s here — `TaskWaitMcpTools` clamps its own timeout to 170 s to stay
|
||||
comfortably under it (see [external-mcp.md](external-mcp.md)).
|
||||
|
||||
### Auto-restart after the usage limit
|
||||
|
||||
A handler session that dies mid-phase used to be a silent dead tile: `InteractiveTerminalViewModel.
|
||||
OnSessionProcessExited` only raised a visible banner for a death **inside** its 5 s
|
||||
`StartupGraceWindow`. It now raises `ExitedAfterStartup` for a later non-zero exit; the pane
|
||||
forwards it as `SessionDiedAfterStartup(taskId, exitCode)` and `MissionControlViewModel` reports it
|
||||
via `WorkerHub.ReportInteractiveSessionExited`. Every task-based pane reports — the UI does no
|
||||
filtering, because the exit code is the *only* thing it knows.
|
||||
|
||||
`HandlerRestartCoordinator` (Worker) decides the rest and **fails closed** — unlike the usage gate,
|
||||
every missing signal means "don't relaunch":
|
||||
|
||||
- **Which round was running** comes from `HandlerRoundState`, a JSON file at
|
||||
`~/.claudeDo/handler-rounds.json` (one `HandlerRound` per handler task id, deliberately not a DB
|
||||
column). `BuildForMergeHelperAsync` writes the opening round as phase `"triage"`;
|
||||
`BuildForMergeHelperHandoffAsync` writes each subsequent `MergeHelperPhase`. Without it the phase
|
||||
and task set exist *only* in the transient `HandoffRequested` broadcast, so a dead tile could not
|
||||
be reopened. A `"triage"` round is never reopened — there is no handoff spec for it.
|
||||
- **Was it really the usage limit?** A ConPTY session has no captured stdout, so `TaskRunner.
|
||||
IsUsageLimitMessage` (which matches the CLI's own "session limit" text) has nothing to read. The
|
||||
corroborating witness is `UsageState.Snapshot.FiveHour.Utilization ≥ 95`
|
||||
(`HandlerRestartCoordinator.UsageLimitProofPct`) at the moment of death. A crash at 40 % is not
|
||||
restarted; no snapshot at all is not restarted.
|
||||
- **PowerUse only** — both keys, exactly like the prompt addendum above.
|
||||
- **One restart per round**, claimed via `HandlerRoundState.TryClaimAutoRestart` before the wait, so
|
||||
a restart that dies again stays dead. `HandlerRoundState.Save` carries `AutoRestartUsed` over when
|
||||
the phase is unchanged, because the restart's own launch-spec build writes the round again — a
|
||||
naive overwrite would hand out a fresh budget every time and loop all night.
|
||||
- The restart waits out `FiveHour.ResetsAt`, then re-broadcasts `HandoffRequested` with the
|
||||
persisted phase + task set, which the UI already handles by reopening the tile. It goes **around**
|
||||
`handoff_list_handler`, so `HandoffRoundTracker.Register` is never called and the run loses no
|
||||
handoff budget.
|
||||
|
||||
Sibling to `UsageLimitAutoContinueCoordinator`, not an extension of it: that one only ever sees
|
||||
headless runs (`FailureReason == "usage_limit"`, resumed through the override slot), and a ConPTY
|
||||
session has no `task_runs` row to classify.
|
||||
|
||||
### The host task and its commit range
|
||||
|
||||
The handler run **owns a real ClaudeDo task**, created by `CreateMergeHelperTask` (hub) →
|
||||
|
||||
Reference in New Issue
Block a user