fix(web): restore a submitted message to the composer when its turn fails - #7415
fix(web): restore a submitted message to the composer when its turn fails#7415sideeffffect wants to merge 4 commits into
Conversation
…ails Sending a message clears the composer immediately. If the server accepted the turn and it then failed asynchronously — a runtime stream error, a stale pending provider callback, a runtime.error — the failure only surfaced later via session.lastError, long after onSend returned. The inline send-failure path never ran, so the typed text was cleared and lost for good. Keep a snapshot of the submitted text (plus images and contexts) alive until the turn is confirmed accepted-and-clean, and restore it into an empty composer when the turn's session enters an error state. The restore decision is a pure helper (deriveTurnFailureRecoveryAction) keyed on session.status rather than the carried-forward lastError, so a stale prior-turn error cannot trigger a spurious restore. The synchronous and asynchronous failure paths now share one restore routine. Claude Opus 4.8 via Claude Code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces new runtime behavior for automatic message recovery on async turn failures, with complex state machine logic handling multiple edge cases. The scope exceeds a simple bug fix and warrants human review given the behavioral implications. You can add or adjust custom eligibility rules. Learn more. |
- Arm the pending-send snapshot before the async send RPCs instead of after startThreadTurn resolves. The recovery effect keys off activeServerThread, not the ref, so a failure that lands during the awaits would otherwise run the effect with no snapshot and never restore the composer. A synchronous send failure clears the snapshot again. - Treat a moved latestTurnCompletedAt as clean completion, not just a new turn id. A steered follow-up folds into the running turn and keeps the same id, so the snapshot would otherwise linger and could restore an already-sent message on a later, unrelated session error. Claude Opus 4.8 via Claude Code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to review of the recovery snapshot: - Arm the snapshot only after the turn is accepted (not before the send RPCs), and bump a state tick so the recovery effect re-evaluates even if the failing session state already landed during the awaits. Arming early let the effect restore mid-onSend, after which a synchronous failure saw a non-empty composer and left the optimistic user message orphaned in the transcript. - Treat a changed latestTurnId as session advancement in addition to a changed sessionUpdatedAt, so an accept-then-fail that shares the pre-send millisecond timestamp still restores. Claude Opus 4.8 via Claude Code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A prior turn's recovery snapshot stayed live while a follow-up or steer send cleared the composer and awaited its RPCs. If the session hit error in that window the effect restored the old snapshot into the now-empty composer, and a later synchronous failure for the new send then skipped restore (composer no longer empty), losing the newly typed text. Drop any pending snapshot at each point onSend takes over the composer (main send, plan follow-up, standalone slash command); the main send re-arms its own snapshot once its turn is accepted. Only the most recent send is recoverable, which matches the single-snapshot model. Claude Opus 4.8 via Claude Code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d9aa8ff. Configure here.
Problem
When you submit a message in a conversation, the composer is cleared immediately. If the server accepts the turn and it then fails asynchronously — e.g.
Claude runtime stream failed.,Stale pending user-input request: … Provider callback state does not survive app restarts or recovered sessions., or a bareRuntime error— the failure surfaces only later viasession.lastError, long afteronSendhas returned. The existing send-failure restore path only runs for a synchronous start-turn RPC rejection, so in the async case the typed text stays cleared and is lost for good, with no way to retrieve it.Fix
Keep a snapshot of the just-submitted text (plus images and terminal/element/preview/review contexts) alive until the turn is confirmed accepted-and-clean, and restore it into an empty composer when the turn's session enters an error state.
deriveTurnFailureRecoveryActioninChatView.logic.ts.session.status === "error"(a fresh, current-state signal), not onlastError— the server carrieslastErrorforward across a new turn until the session next reachesready, so keying on it would restore a stale prior-turn error. Asession.updatedAtguard ensures a session that was already inerrorat send time can't trigger a spurious restore before the new turn begins.Scope: the primary
onSendpath in an existing/started conversation (the reported case). Plan-mode follow-up and "implement in new thread" are intentionally out of scope for this focused change.Verification
apps/webtypecheck, lint, and the touched unit tests pass, including six new cases forderiveTurnFailureRecoveryAction(async-failure restore, retyped-composer drop, stale-error no-op, clean-completion drop, still-running wait).This is a state/logic fix rather than a visual one, so there is no meaningful before/after screenshot; the difference is whether the composer is repopulated after an induced runtime failure. Happy to attach a short screen recording of a forced runtime error if useful.
Claude Opus 4.8 via Claude Code.
Note
Medium Risk
Touches core send/composer state in ChatView with nuanced session/turn timing; logic is unit-tested but wrong edge cases could restore stale text or drop recovery incorrectly.
Overview
Fixes lost composer text when the server accepts a message but the turn fails later (runtime stream errors, stale callbacks, etc.)—cases the synchronous start-turn failure path never handled.
Adds
deriveTurnFailureRecoveryActioninChatView.logic.tsto deciderestore,drop, orwaitfrom pre-send session/turn markers and current thread state. Recovery keys onsession.status === "error"with proof the session advanced (updated timestamp or new turn id), not onlastError, and skips restore if the user already retyped.ChatView.tsxkeeps a post-accept snapshot inpendingSendRecoveryRef, runs a recoveryuseEffect(withrecoveryReevaluateTickfor in-flight failures), andrestoreComposerContentFromSnapshotshared with the existing synchronous send-failure path.Reviewed by Cursor Bugbot for commit d9aa8ff. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Restore submitted message to composer when an accepted turn later fails
deriveTurnFailureRecoveryActionfunction in ChatView.logic.ts determines whether torestore,drop, orwaitbased on pre-send session/turn markers versus current state.ChatViewContentcaptures a recovery snapshot (prompt, attachments, contexts, annotations) and arms it after a successful turn start; auseEffectmonitors thread state changes and triggers restoration when appropriate.restoreComposerContentFromSnapshotcallback instead of inline restoration logic.Macroscope summarized d9aa8ff.