fix(app): add fallback for lost question.asked SSE events#379
Conversation
When the question.asked SSE event is lost (stream disconnect, transient error), the question dock never appears because sync.data.question has no entry for the session. The backend question remains pending, the model waits forever, and the user sees a stuck session. Add a fallback effect in session-composer-state that detects running question tool parts in message parts when questionRequest() is empty, and re-fetches the pending question list from the backend.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughA defensive fallback was added to createSessionComposerState: when a question tool part is running but questionRequest() is absent, it refetches pending questions via sdk.client.question.list(), filters and groups valid QuestionRequest objects, and reconciles them into sync. ChangesDefensive Question Refetch
sequenceDiagram
participant Composer as SessionComposerState
participant SDK as SDK Client
participant Sync as Sync Store
Composer->>Composer: detect running question part without questionRequest
Composer->>SDK: sdk.client.question.list()
SDK-->>Composer: list of pending questions
Composer->>Sync: reconcile grouped/sorted QuestionRequests by id
Sync-->>Composer: state updated
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 6/10 reviews remaining, refill in 20 minutes and 28 seconds. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a fallback mechanism to handle lost SSE events by re-fetching pending questions when a "question" tool is detected in a running state without a corresponding request. Feedback suggests removing the defer: true option to ensure the check runs on component mount, refining the scope and logic of the refetch flag to prevent cross-session blocking, and using a more robust sort comparator for question IDs.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session/composer/session-composer-state.ts`:
- Around line 55-85: The current refetch handler using
sdk.client.question.list() swallows errors and only clears
questionRefetchInflight, leaving state stuck; modify the catch block for the
anonymous subscription callback (the function keyed by sessionID that sets
questionRefetchInflight) to schedule a bounded retry/backoff or re-arm a
reactive retry trigger keyed by the same sessionID: on failure increment a small
retry counter or use setTimeout with exponential backoff (capped attempts),
ensure questionRefetchInflight is false between attempts, and re-invoke the same
fetch logic (the sdk.client.question.list() flow that updates
sync.set("question", sid, reconcile(...))) until success or max retries;
reference variables/functions: sdk.client.question.list,
questionRefetchInflight, sessionID, and the batch/reconcile sync.set logic so
the retry re-runs the same update path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: eee73424-bcec-4df1-91e0-ba4803d11f4f
📒 Files selected for processing (1)
packages/app/src/pages/session/composer/session-composer-state.ts
When the component mounts with a stuck question (e.g. after page refresh), defer:true prevents the fallback from detecting it until a subsequent state change. Removing it allows immediate detection.
Astro-Han
left a comment
There was a problem hiding this comment.
Review response
#1 (high): Remove defer: true — Fixed in a95b823
Good catch. Without defer, the fallback now runs on mount, covering the page-refresh scenario where SSE is always disconnected.
#2 (medium): SessionID inflight tracking — Won't fix (P3, YAGNI)
Having two sessions simultaneously stuck on question tools requires the model to call question in two separate sessions at the same time, which is architecturally impossible (question blocks the turn until answered). A boolean flag is sufficient.
#3 (medium): Sort comparator completeness — Won't fix (P3, YAGNI)
QuestionIDs are unique ascending timestamps. Equality never occurs in this domain. The comparator is correct for the data.
#4 (major): Retry on failed fetch — Won't fix for now (P2, double-failure)
This would require both the SSE event AND the fallback HTTP request to fail independently. Even if it happens, any user interaction (sending a message, switching sessions) changes reactive dependencies and naturally retriggers the effect. Adding retry/backoff logic for this extreme case would violate YAGNI — can revisit if actually reported.
Summary
Add a self-healing fallback in
session-composer-statethat detects runningquestiontool parts in message parts when thequestionRequest()memo is empty, and re-fetches the pending question list from the backend viasdk.client.question.list().Why
The question dock relies exclusively on
sync.data.questionto render, which is populated by two paths:sdk.question.list()called once at startupquestion.askedreal-time pushIf the SSE event is lost (stream disconnect, transient error, reconnect gap), the question data never enters the frontend store. The dock never renders, the model waits forever for an answer, and the session appears stuck — even though the backend question is still pending and the message parts already contain the
questiontool inrunningstate.This was observed in a real session where the model called the
questiontool, the backend dispatched it (state = running), but the frontend never showed the question dock. The session froze with no way to recover.Related Issue
None.
Human Review Status
Pending.
Review Focus
session-composer-state.tslines 32–89: correct detection of running question parts, proper guard against re-fetch loops, correct store update viasync.set+reconcileRisk Notes
questionRefetchInflightprevents duplicate fetches but is module-scoped (shared across session switches). This is intentional — re-fetches are rare and a single inflight guard is sufficient.How To Verify
Screenshots or Recordings
Not applicable — no visible UI change. The fix ensures the question dock appears when it should.
Checklist
dev, and my PR title and commit messages use Conventional Commits in EnglishArchitectural Follow-up
This fix treats the symptom (missing question data) at the UI layer. The root cause is that the SSE event stream has no replay/recovery mechanism. Consider:
Summary by CodeRabbit