Repository navigation
fix(desktop): latch a resume that paints nothing when the list row is stale - #124789
Merged
austinpickett merged 1 commit intoSep 27, 2026
Merged
Conversation
… stale
The resume-failure latch ("this window is stranded, retry it") armed only
when the cached sessions-list row reported message_count > 0. That row is a
cache of backend truth and lags the two flows that report a vanished thread:
after a sleep/wake reconnect the list can still carry the respawned
backend's session at 0 rows, and a context-compression tip can show 0 rows
while the stored transcript is intact. Either way the cold resume painted an
empty thread with an ACTIVE runtime and no latch at all — no auto-retry, no
error surface, just a silently blank chat that looks like lost history.
The resume RPC is authoritative and always reports the stored transcript
size (tui_gateway fills message_count from state.db even when
messages_omitted), so treat it — and a non-empty REST page — as the other
rungs of the same ladder. A resume that paints nothing now arms the retry
latch whenever ANY source says the session has a transcript, so the window
recovers through use-route-resume's bounded retry (and surfaces an explicit
error + manual Retry once exhausted) instead of going silently blank.
Fixes NousResearch#82806
Fixes NousResearch#83154
Duplicate of #83802: its open broader resume repair already uses response message_count to arm the empty-transcript retry latch when sidebar state is stale or absent. |
9 tasks
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.
What does this PR do?
Fixes one root cause behind a session-resume flow that can leave the primary thread blank with no way out.
resumeSession's cold path computes the final transcript and, when it comes up empty, arms$resumeFailedSessionIdsouse-route-resumere-attempts the resume (bounded backoff, then an explicit error + manual Retry). That latch armed only when the cached sessions-list row reportedmessage_count > 0:storedcomes from$sessions/resolveStoredSession— it is a cache of backend truth, and it lags exactly the two flows that report a vanished thread:message_count 0while the backend's stored transcript is intact.In both cases a resume that paints nothing falls past the latch, then sets
activeSessionIdto the resumed runtime and publishes an empty message set. The thread is not "loading" (routedSessionIsLoadingreturns false onceactiveSessionIdis set and the row has no known history), so the UI shows an empty chat with no error and no retry — history looks lost even though the session is alive.The resume RPC is the authoritative source and always reports the stored transcript size —
tui_gateway/methods_session.py:814fillsmessage_countfrom the stored size even whenmessages_omittedis set for Desktop's cold resume. This PR treats it (and a non-empty REST page) as the other rungs of the same ladder:A resume that paints nothing now arms the retry latch whenever any source says the session has a transcript, so the window self-heals through
use-route-resume's bounded retry and surfaces an explicit error + manual Retry once exhausted, instead of going silently blank. The provisional cached-tail rollback inside the branch is unchanged.Common root cause, one PR
Both reports are the same flow (
use-session-actions/index.ts) where a resume that ends empty leaves the user on a blank thread; the single widened precondition covers both. No second code path was needed.Parts of each report NOT covered here (deliberately)
if (persisted && persistedMatchesActivatedSession && (persisted.messages.length || !activatedMessages.length))— is present at
index.ts:1613. fix(desktop): stop empty REST transcript refresh from wiping a warm resume #82843 is closed unmerged, but its change landed another way. This PR does not re-touch it.goneSessionVerdict → 'draft'(RPC + REST both 404, id verifiably dead). Dropping to a fresh draft without a toast there is the documented contract (apps/desktop/AGENTS.md: "the window drops to a fresh draft without toasting or looping"), and it already carries the stashed composer draft over viaannounceGoneSessionDraft. Changing it would contradict that contract; the reported defect is the unlatched blank with a live runtime, which this PR fixes.session.resume": already done atindex.ts:1950("Paint the persisted transcript as soon as REST returns …", fix(desktop): paint prefetched history during cold resume #90130). TheisCurrentResume()bails at 1979/2234/2251/2278/2343 are stale-navigation guards — returning without painting is correct there, since a newer navigation owns the view.Behavior
use-route-resumere-attempts with backoff and, if it stays stranded, the chat shows the explicit error + Retry state.Acceptance coverage
Two regression tests in
apps/desktop/src/app/session/hooks/use-session-actions.test.tsx(both fail on pre-fix source, pass with it):arms the failure latch when a stale list row hides history the resume RPC still reports— [Bug]: Desktop can place newest user bubble after completed reply following context compression #83154: list rowmessage_count: 0,session.resumereturnsmessage_count: 12withmessages_omittedand an empty REST page → latch arms,activeSessionIdstays null.arms the failure latch when a wake warm-resume bail falls to an empty cold resume— [Bug]: macOS Desktop — after sleep/reopen, previous prompts and right-hand chat timeline disappear #82806: a warm cache is bound,session.activate404s (respawned backend), the mapping is dropped, the cold resume paints nothing, list row stale → latch arms.Validation (real output)
Proving the tests are the fix (source file reverted, tests kept):
No macOS hardware was available to physically exercise sleep/wake; the tests drive the exact code path (warm bind → activate 404 → cold resume painting nothing) and the assertions are on the observable latch/active-session state, not on internal calls.
Reused work
persisted.messages.length || !activatedMessages.lengthguard) framed where the residual primary-thread gap is.Fixes #83154
hermes-agent #82806 is not closed by this PR. Its original mechanism — the warm-path empty-REST
wipe — is already fixed on
main(PR #82843'spersisted.messages.length || !activatedMessages.lengthguard, credited above); this PR does not re-touch it. What it adds is the failure latch for the
sleep/wake warm-bail flow, i.e. the residual that left the thread blank with no retry. Whether that
fully resolves #82806 is a maintainer call — leaving it open until someone confirms on a build with
this change.
🤖 AI-assisted (Hermes Agent); verification above is real command output.