fix(frontend): only offer Retry for failures resending the prompt can fix - #263
Merged
Conversation
… fix The composer banner shows one "Retry last message" button for every failure it can display, and most of those are not generation failures. Clicking it after an unrelated error resent a prompt the thread had already answered, adding a duplicate turn. Three of the six sites that raise this banner cannot be fixed by resending: - a failed fork -- not a generation at all; the thread is answered and unchanged; - a failed stop -- the response is still running, so a resend starts a second one alongside it; - a reload that failed after generation finished -- the answer exists and was saved, only the refetch failed. The other three are genuine: a generation that failed, a runtime that was unavailable, and an admission that could not be started. Those keep Retry, and the replay logic behind it is untouched. `ScopedError` now records whether resending is the remedy, and the button is rendered only for an error that says so. The regression test sends and completes a turn before forking, on purpose: `lastPrompt` is only set once something has been sent this session, so a test that forks in a freshly loaded thread never renders a Retry button and passes against the unfixed code. My first version did exactly that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
The problem
The composer banner renders one "Retry last message" button for every failure it can show — and most of those are not generation failures. Clicking it after an unrelated error resends a prompt the thread has already answered, adding a duplicate turn.
The clearest case: fork a message, the fork fails, the banner says "Could not fork this chat." — and offers to resend your last prompt.
Root cause
onRetrywas gated only on whether a prompt had ever been sent:Six code paths raise this banner. Three of them cannot be fixed by resending:
And
retryLastPromptmakes it concrete. With no dangling user turn — the normal state of an answered thread — it falls through to the ambiguous-admission branch and callsstartGeneration(lastPrompt, ...), submitting the old prompt as a new message.The other three sites are genuine and keep Retry: a generation that failed, a runtime that was unavailable, and an admission that could not be started. The replay/idempotency logic behind them is untouched.
The fix
ScopedErrorrecords whether resending is the remedy, and the button renders only for an error that says so:Each of the six call sites now states its own answer at the point where it knows it, rather than the banner guessing.
A note on the test
The regression test sends and completes a turn before forking, deliberately.
My first version simply loaded a thread with existing messages and forked. It passed against the unfixed code — because
lastPromptis only set insidestartGeneration, so a thread that was merely loaded never renders a Retry button at all, andqueryByRole(...).not.toBeInTheDocument()was trivially satisfied. The test now establisheslastPromptfirst, so the button genuinely would appear without the fix.Against the unfixed code it fails; the three existing tests that exercise Retry on a real generation failure pass throughout, which is the half that must not regress.
npm test -- --runnpm run typechecknpm run lintpython -m pytest -qCompatibility and rollback
One component's local state and one render condition. No API contract, stored data, or migration. Every error still displays and still dismisses; only the Retry affordance is withheld where it could not have helped. Reverting the commit restores the previous behaviour exactly.
Limits
One test covers the fork path. The stop-failure and reload-failure sites take the same single mechanism and are not separately covered — worth adding if either grows more logic, but three near-identical integration tests for one boolean seemed like poor value.
A failed fork now offers no action at all beyond dismissing the banner. Retrying the fork would be the useful button there, and that is a feature rather than part of this fix.
🤖 Generated with Claude Code