0822d41372
* fix(backend): drain orphaned queued messages on agent boot ready When autoStartStepPrompt hits a transient PromptTask error (e.g. "agent stream disconnected" mid-prompt), it records the auto-start user message and then queues the same prompt for retry. The drain only fires from handleAgentReady (turn-end) — but the failed prompt never reached the agent, so no agent.ready ever arrives for it. After the user resumes the session, handleAgentBootReady only flipped state and returned, leaving the workflow auto-start prompt stuck on the queue forever (the "1 queued" that wouldn't go away after resume). handleAgentBootReady now drains via drainQueuedMessageAfterTransition after the WAITING_FOR_INPUT flip — also on the early-return path where state was already WAITING (boot signal raced persistResumeState). Terminal sessions short-circuit before the drain so cancelled/completed sessions don't revive their queued prompts. Tests: TestAutoStartTransientError_BootReadyDrainsOrphanedQueue reproduces the production scenario end-to-end (transient PromptAgent failure → record + queue → boot_ready → drained). Plus TestHandleAgentBootReady_DrainsOrphanedQueuedMessage covers both STARTING→WAITING and the persistResumeState-race path, and TestHandleAgentBootReady_DoesNotDrainForTerminalSession guards the terminal-session short-circuit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(backend): wait for drain goroutine and verify prompt dispatch in repro Greptile flagged that the queue-empty assertion in TestAutoStartTransientError_BootReadyDrainsOrphanedQueue raced the drain's executeQueuedMessage goroutine: the goroutine would re-enter the same isTransientPromptError path and re-queue the message immediately, but the assertion would usually win on timing. Clear agentMgr.promptErr before Phase 2 (simulating the agent recovering), then poll capturedPrompts until the drained prompt actually reaches PromptAgent. This turns a timing-fragile assertion into a deterministic end-to-end check: drain → executeQueuedMessage → PromptTask → PromptAgent → queue empty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(backend): wire executor in boot-ready drain test to fix nil-deref under -race Claude and cubic flagged that TestHandleAgentBootReady_DrainsOrphanedQueuedMessage constructed Service without an executor field. The drainQueuedMessageAfterTransition goroutine spawned by handleAgentBootReady would nil-deref s.executor via PromptTask -> ensureSessionRunning -> s.executor.GetExecutionBySession. Passed today by luck of scheduling but would panic under -race. Wire a real executor (built from the same mockAgentManager and repo as the other tests) and seed an executors_running row so the drained PromptTask lands on a working code path instead of crashing. Verified passes with `go test -race -count=3`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(backend): replace time.Sleep polling with promptDone channel sync in repro Claude flagged that the polling loops in TestAutoStartTransientError_BootReadyDrainsOrphanedQueue used time.Sleep where mockAgentManager.promptDone (closes on first PromptAgent call) gives a deterministic sync point per AGENTS.md. - Phase 1: wait on firstPromptCalled, then a 500ms / 1ms tight poll covers the synchronous PromptAgent-returns → queue-row-commits gap. - Phase 2: swap in a fresh secondPromptCalled channel (the mock's `first := len(capturedPrompts) == 0` guard requires resetting captured* slices so the close re-fires for the drain's second call), then wait on that signal before the queue-empty assertion. Verified with `go test -race -count=5` — all pass, no goroutine leaks. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>