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':