Repository navigation
Conversation
…after a failed resume A saved thread resumes by asking the server for the events it missed. If that replay hit a defect, the subscription stopped for good and every relaunch replayed the same events from the saved cursor. The next attempt now loads the full snapshot instead. Closes pingdotgg#19563 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a narrowly scoped client-runtime bug fix that recovers cached threads through one full snapshot after a failed replay, while keeping unrecoverable snapshot failures terminal. Regression tests cover recovery, error suppression, cursor advancement, and the double-failure case without changing schemas, defaults, or static-analysis settings. You can add or adjust custom eligibility rules. Learn more. |
kvnloo
left a comment
There was a problem hiding this comment.
Thanks for the focused recovery path and the Android before/after test; the cached-resume defect case is well covered.
One question about the linked report's failure mode: #19563 records subscribeThread ending with Interrupted / ClientAbort, whereas subscribeDynamicMapped only invokes onDefect for Die or RpcClientDefect (packages/client-runtime/src/rpc/client.ts). This PR's fallback flag is set by onDefect, and the new positive test injects Effect.die. Is the interrupted/aborted subscription in the report expected to pass through that same callback? If not, a focused interruption/reconnect reproduction would help distinguish the verified replay-defect fix from the reported stuck-hydration case before treating #19563 as fully closed.
Not requesting a broader redesign—just checking the causal link between the observed trace and this tested recovery. Credit to @hichish for the original trace and @shivamhwp for the focused fix and mobile evidence.
|
Taking over this one. I’ll reduce the tests to the useful coverage, update the implementation against current main, and verify it before publishing the changes. |
…ot be decoded A resumed thread subscription replays the events after the saved cursor. When that replay dies, the same cursor dies again on every reconnect and relaunch, so the thread stays on "Syncing messages..." until the cache is cleared. A failed resume now makes the next attempt load the full snapshot, which moves the cursor past the bad events. The sync error is published only where the attempt is given up, so a recovered resume never flashes it and a defect on a snapshot attempt stays terminal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The recovery test only inspected the final live state, so publishing a transient error during the failed resume would still have passed. It now consumes every observed state from the defect through live and asserts none of them carries an error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@kvnloo An interruption-only client cause does not invoke The #15937 already addresses unknown event types server-side. This takeover is scoped as client-side recovery for other replay defects: a failed cursor-based replay retries once from a full snapshot; a snapshot defect reports a terminal error. Ordinary client interruption is passed through, and the PR references #19563 rather than claiming its exact cause was reproduced. At rebuilt head The actual web app was also checked with the same one-shot synthetic replay failure on baseline |
Dismissing prior approval to re-evaluate e4c9b46
|
The linked-issue warning identifies an unresolved part of #19563, not a claim this PR makes. The body uses “Refs,” explicitly says the original Interrupted/ClientAbort cause is unconfirmed, and labels the live proof as an injected replay defect. It does not claim to close that issue. The retry branch deliberately passes interruption-only causes through. Retrying cancellation would also restart intentional subscription teardown. Failed resumed replay instead requests a full snapshot; a failed snapshot remains terminal. The two focused regressions and the recorded web transport probe cover that narrower behavior, including the subsequent cursor-based resume and absence of a transient error. I am declining the request to add ClientAbort recovery to this PR without a demonstrated causal path. The original issue remains open for that investigation; expanding the retry predicate based only on its server-side symptom could introduce unwanted subscriptions during teardown. |
Problem
A saved thread can remain stuck on “Syncing messages…” when replaying events from its saved cursor fails. Relaunching asks for the same failed replay again.
Change
Retry a failed resumed subscription once using a full thread snapshot. Keep snapshot failures terminal so recovery cannot loop, and use the new snapshot cursor for later sessions. Report a replay failure only if recovery gives up, avoiding an error banner during successful snapshot recovery.
Scope and approval
This is a maintainer-requested takeover of the focused thread-sync bug fix. The shared client runtime serves web, desktop, and mobile. Refs #19563. Based on the original work by @shivamhwp.
The original report’s precise failing event is unconfirmed. #15937 already handles unknown event types on the server; this change adds client recovery for other replay defects.
Verification
49 focused sync tests, the client-runtime typecheck, and scoped lint/format checks pass. The two regression cases verify snapshot recovery, later cursor-based resumption, and terminal snapshot failure. All observed recovery states are checked for the absence of a transient error. Reintroducing transient error publication or retrying every defect fails the corresponding test.
The actual web app was tested against the same temporary one-shot replay failure on baseline
07d38b8dand rebuilt heade4c9b469. The baseline requested cursor 18 and remained at “Syncing messages…”. The head requested cursor 18, recovered through a full snapshot, then successfully resumed from cursor 18 on a later reconnect. No error banner was observed during recovery. The temporary server probe was removed afterward; no provider turn or database write was needed.This was a synthetic replay failure, not a reproduction of the original
Interrupted/ClientAbortreport. Native clients were not run; they share the changed client runtime. The terminal double-failure path was verified by focused tests, not the recording.Before — cached thread remains syncing
After — snapshot recovery clears syncing
Recovery recording
Taken over with Claude Opus 5.5 (tests and review), Claude Fable 5.1 (implementation), and GPT-6.1 Sol (review) through T3 Code; coordinated with Codex.