Skip to content

Fix freebuff grace-period hang where UI looks stuck streaming#518

Merged
jahooma merged 2 commits intomainfrom
jahooma/grace-period-fix
Apr 20, 2026
Merged

Fix freebuff grace-period hang where UI looks stuck streaming#518
jahooma merged 2 commits intomainfrom
jahooma/grace-period-fix

Conversation

@jahooma
Copy link
Copy Markdown
Contributor

@jahooma jahooma commented Apr 19, 2026

Summary

Two separate hang vectors after a freebuff session ends mid-run, both reported as "task keeps running then just hangs, only option is to close the terminal":

  • handleFreebuffGateError never finalized the AI message. For `session_expired`/`waiting_room_required`, the handler only flipped session state, leaving `isComplete: false` on the in-flight message. The streaming cursor kept rendering and the batched-updater flush interval leaked — users saw an apparently still-streaming message next to the rejoin banner and waited indefinitely. Now calls `updater.markComplete()`.
  • `ask_user` was unreachable while the session-ended banner was up. If the agent was mid-`ask_user` when the session ended, the banner replaced the ChatInputBar and hid the answer form, so the run was waiting on input that could never arrive. Chat.tsx now lets `ask_user` take precedence over the banner (same pattern as review mode).

Test plan

  • Run a freebuff session, wait for grace to expire mid-agent-run, verify the last AI message visibly finalizes and the rejoin banner is clearly actionable.
  • Trigger an `ask_user` during grace period, verify the answer form is reachable and the agent can complete.

🤖 Generated with Claude Code

Two separate hang vectors after a freebuff session ends mid-run:

1. `handleFreebuffGateError` for session_expired/waiting_room_required
   only flipped session state — it never finalized the in-flight AI
   message. Result: `isComplete` stayed false, the streaming cursor kept
   rendering, and the batched-updater flush interval leaked. Users saw
   an apparently still-streaming message next to the rejoin banner,
   assumed the agent was working, and waited indefinitely. Now calls
   `updater.markComplete()` so the message visibly finalizes.

2. If the agent was mid-`ask_user` when the session ended, the
   SessionEndedBanner replaced the ChatInputBar and hid the answer form,
   leaving the agent waiting on input that could never arrive. Chat.tsx
   now lets ask_user take precedence over the banner (same pattern as
   review mode) so in-flight runs can still finish during grace.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 19, 2026

Greptile Summary

This PR fixes two distinct UI hang scenarios that occur when a freebuff session ends mid-agent-run. The changes are minimal, well-scoped, and follow existing patterns in the codebase.

Key changes:

  • send-message.ts: Adds updater.markComplete() before markFreebuffSessionEnded() in the session_expired/waiting_room_required error cases, so the in-flight AI message is finalized (setting isComplete: true), stopping the blinking cursor and disposing the batched-updater flush interval.
  • chat.tsx: Changes the SessionEndedBanner condition from isFreebuffSessionOver to isFreebuffSessionOver && !askUserState, mirroring the existing reviewMode precedence pattern so the ask_user form remains reachable even when the session has ended — otherwise the agent hangs waiting for input that can never be submitted.

Confidence Score: 5/5

Safe to merge — two tightly-scoped bug fixes with no regressions introduced.

Both changes are minimal one-liners targeting clearly identified hang paths. They follow existing, well-tested patterns (reviewMode precedence, markComplete() usage at interruption sites). markComplete() is idempotent and optional-metadata safe, and the code paths are mutually exclusive so double-call is not possible. No new state, no new side effects.

No files require special attention.

Important Files Changed

Filename Overview
cli/src/hooks/helpers/send-message.ts Adds updater.markComplete() to the session_expired/waiting_room_required branch to finalize the AI message and dispose the flush interval, fixing the stuck-cursor hang. Safe: markComplete() accepts optional metadata, is idempotent on isComplete, and the error path returns immediately so no double-call with the normal completion at line 433.
cli/src/chat.tsx Changes isFreebuffSessionOver to isFreebuffSessionOver && !askUserState so the ask_user form inside ChatInputBar takes precedence over SessionEndedBanner, exactly mirroring the existing reviewMode pattern. Once the user submits the form, askUserState returns to null and the banner reappears as expected.

Sequence Diagram

sequenceDiagram
    participant Agent as Agent (server)
    participant SM as send-message.ts
    participant Updater as BatchedMessageUpdater
    participant Store as ChatStore
    participant UI as chat.tsx

    Agent->>SM: session_expired / waiting_room_required error
    SM->>SM: handleFreebuffGateError()
    SM->>Updater: markComplete() NEW
    Note over Updater: flush() → dispose() → isComplete=true
    Updater->>Store: message.isComplete = true
    Store->>UI: streaming cursor stops
    SM->>Store: markFreebuffSessionEnded()
    Store->>UI: isFreebuffSessionOver = true

    alt askUserState is active (ask_user mid-flight)
        UI->>UI: show ChatInputBar (MultipleChoiceForm) NEW
        Note over UI: banner suppressed via !askUserState guard
        UI->>SM: user submits answer
        SM->>Store: askUserState = null
        UI->>UI: show SessionEndedBanner
    else no pending ask_user
        UI->>UI: show SessionEndedBanner immediately
    end
Loading

Reviews (1): Last reviewed commit: "Fix freebuff grace-period hang where UI ..." | Re-trigger Greptile

@jahooma jahooma merged commit 711f40c into main Apr 20, 2026
34 checks passed
@jahooma jahooma deleted the jahooma/grace-period-fix branch April 20, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant