diff --git a/cli/src/chat.tsx b/cli/src/chat.tsx index bafdcecf1..af83a45c9 100644 --- a/cli/src/chat.tsx +++ b/cli/src/chat.tsx @@ -1473,15 +1473,17 @@ export const Chat = ({ )} {reviewMode ? ( - // Review takes precedence over the session-ended banner: during the - // grace window the agent may still be asking to run tools, and - // those approvals must be reachable for the run to finish. + // Review and ask_user take precedence over the session-ended banner: + // during the grace window the agent may still be asking to run tools + // or asking the user a question, and those approvals/answers must be + // reachable for the run to finish — otherwise the agent hangs + // waiting for input that can never be given. - ) : isFreebuffSessionOver ? ( + ) : isFreebuffSessionOver && !askUserState ? ( diff --git a/cli/src/hooks/helpers/send-message.ts b/cli/src/hooks/helpers/send-message.ts index 01f6880b6..02e419b30 100644 --- a/cli/src/hooks/helpers/send-message.ts +++ b/cli/src/hooks/helpers/send-message.ts @@ -510,10 +510,16 @@ function handleFreebuffGateError( switch (kind) { case 'session_expired': case 'waiting_room_required': - // Our seat is gone mid-chat. Flip to `ended` instead of auto re-queuing: - // the Chat surface stays mounted so any in-flight agent work can finish - // under the server-side grace period, and the session-ended banner - // prompts the user to press Enter when they're ready to rejoin. + // Our seat is gone mid-chat. Finalize the AI message so its streaming + // indicator stops — otherwise `isComplete` stays false and the message + // keeps rendering a blinking cursor forever, making the user think the + // agent is still working even though the SessionEndedBanner is visible + // and actionable. Also disposes the batched-updater flush interval. + updater.markComplete() + // Flip to `ended` instead of auto re-queuing: the Chat surface stays + // mounted so any in-flight agent work can finish under the server-side + // grace period, and the session-ended banner prompts the user to press + // Enter when they're ready to rejoin. markFreebuffSessionEnded() return case 'waiting_room_queued':