feat: real recovery UI for generation failures - #55
Merged
Conversation
Item #4 from the consolidated fix plan. Root cause (accountant audit round three, David's Division 820 question - three identical retries, all "Query failed - please try again", /health reporting fine throughout): an unhandled exception inside the SSE generate() loop just crashed the async generator. FastAPI closed the connection with no event, EventSource fired a bare onerror, and the dashboard showed a generic message with zero distinction between "the AI provider is briefly at capacity" and a real bug - and no retry action, just a one-line toast. Backend: the astream loop is now wrapped in a try/except. On failure, the query row is marked failed (mirroring the existing embed-failure handling), and a typed `error` SSE event is emitted before a normal [DONE] - so EventSource resolves cleanly instead of erroring. Classifies transient vs not via litellm's exception hierarchy: every provider's rate-limit/timeout/connection/5xx failures normalise onto openai's exception classes (RateLimitError, Timeout, APIConnectionError, etc. all subclass openai.APIError), a real checkable signal rather than guessing from message text. Frontend: a new `generationError` state (message, transient, the question that failed) renders an actual card with a "Try again" button that re-asks the same question, instead of collapsing into the generic error string every other unrelated save/edit/promote flow also uses. The pre-existing outer catch (a dropped connection before any typed event arrives) now populates the same state instead of a bare toast, so the retry action is available for that failure mode too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Item #4 from the consolidated fix plan - the highest-severity item, deliberately scoped as its own PR given its size relative to #1/#2/#3.
Root cause (accountant audit round three - David hit "Query failed - please try again" three times in a row on an identical retry, with the backend's own /health reporting healthy throughout): an unhandled exception inside the SSE generate() async generator in /query/stream just crashed it. FastAPI closed the connection with no event; the frontend's EventSource.onerror fired with zero information, and the dashboard showed a generic message - no distinction between "provider is briefly at capacity" (worth retrying) and a real bug, and no retry action beyond a one-line toast.
Backend (query.py): the astream loop is wrapped in try/except. On failure:
Frontend (query/page.tsx): a new generationError state renders an actual card (message + context-appropriate note + a "Try again" button that re-asks the same question), instead of collapsing into the generic error string other unrelated flows (save/edit/promote) share. The pre-existing outer catch (a dropped connection before any typed event arrives) now populates the same state, so the retry action is available for that failure mode too.
Verification
Reviewer notes
🤖 Generated with Claude Code