From 9d3466885813791119e6d3bf72460eb0b623f894 Mon Sep 17 00:00:00 2001 From: Sven Olsen Date: Fri, 8 May 2026 14:34:11 -0700 Subject: [PATCH] fix(agent): release activeRequests before publishing TypeAgentFinished MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I keep hitting a bug where a turn finishes -- desktop notification fires, agent is clearly done -- but the terminal stays stuck on the spinner with input blocked until something unrelated wakes the TUI up. The TUI polls IsSessionBusy() inside its TypeAgentFinished handler and only re-evaluates on incoming tea.Msgs. Run() right now publishes the notification while activeRequests still holds the session's cancel func, so the poll on receipt sees true and the busy gates stay stuck. Reorder so cleanup runs first. 💘 Generated with Crush Assisted-by: Claude Opus 4.7 via Crush --- internal/agent/agent.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 3f21fa7f504ebb3fc6212354127fc5f0d15ae62a..66062270c669ffaba298980ff997c6e2c2e04c2e 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -581,16 +581,6 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy return nil, err } - // Send notification that agent has finished its turn (skip for - // nested/non-interactive sessions). - if !call.NonInteractive && a.notify != nil { - a.notify.Publish(pubsub.CreatedEvent, notify.Notification{ - SessionID: call.SessionID, - SessionTitle: currentSession.Title, - Type: notify.TypeAgentFinished, - }) - } - if shouldSummarize { a.activeRequests.Del(call.SessionID) if summarizeErr := a.Summarize(genCtx, call.SessionID, call.ProviderOptions); summarizeErr != nil { @@ -608,10 +598,23 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*fantasy } } - // Release active request before processing queued messages. + // Release active request before publishing the notification. + // TUI handlers poll IsSessionBusy() and only re-evaluate when a + // tea.Msg arrives, so the cleanup must precede the notify or + // subscribers see stale busy state at the moment of receipt. a.activeRequests.Del(call.SessionID) cancel() + // Send notification that agent has finished its turn (skip for + // nested/non-interactive sessions). + if !call.NonInteractive && a.notify != nil { + a.notify.Publish(pubsub.CreatedEvent, notify.Notification{ + SessionID: call.SessionID, + SessionTitle: currentSession.Title, + Type: notify.TypeAgentFinished, + }) + } + queuedMessages, ok := a.messageQueue.Get(call.SessionID) if !ok || len(queuedMessages) == 0 { return result, err