fix(box): a chat agent resumed with no prompt shows as idle, not working

TI
5d ago
+150-13

Bug: A structured (stream-json) agent that is started again without a new prompt lists as running forever: the app shows it working with a timer counting up, until someone sends it something. This happens after its agent exits and the session is resumed, or after the box restarts and its conversation is opened in a new session. Seen on a real box: the last turn ended normally (end_turn), the box rebooted, the conversation was resumed at its prompt, and the session showed "working" for over 90 minutes with berth session turns reporting no turns.

Cause:

  • The host writes its started entry before berthd starts following the log. follow() reads entries already in the log without saying anything (live=false), so the driver's Ready for that start is never sent.
  • reconcile() only corrects running/waiting → Finished. With the turn state empty or exited, nothing is sent.
  • enrich then defaults the session to running (tasks.go).

Fix: Each driver notes when its agent last said it was ready, whether the log was read live or not: Claude when its host started; Codex and ACP when their thread or session opened. The note clears when the host starts again or exits. In reconcile(), a new case: not busy, nothing pending, the agent at its prompt, and turn state "" or exited → send Ready. The turn tracker makes that idle (only when no turn is open). A new session whose host hasn't started yet gets nothing from the catch-up, as before. An earlier version sent Ready there too: that moved its first prompt sooner, and TestATurnClaudeStartsByItselfShowsAsWorking then missed the first turn's short running (17/30 runs). It passes 30/30 now.

Test: TestAResumedConversationWithNoPromptIsIdle, with the stand-in stream Claude. It covers three cases:

  1. The agent stops and the session is resumed: the session lists as idle.
  2. The session is deleted and the conversation is resumed in a new one: it lists as idle.
  3. A prompt there still starts and finishes a turn.

It fails before the fix (AgentState: running with the turn state exited) and passes 10/10 with -race after; TestATurnClaudeStartsByItselfShowsAsWorking passes 30/30. go test -race ./... passes, except TestAttachPassesShiftEnter, which also fails on main without this change. Changelog bullet added under Unreleased.

šŸ¤– Generated with Claude Code