fix(desktop): recover a dead first-submit draft instead of "session not found" - #67503
fix(desktop): recover a dead first-submit draft instead of "session not found"#67503rapsealk wants to merge 3 commits into
Conversation
…ot found" A new chat's state.db row is only persisted by the gateway on its first successful prompt.submit (deliberately lazy, so abandoned drafts never leave "Untitled" rows behind). If the live session dies before that first submit lands — sleep/wake WS drop followed by the orphan reap, a backend restart — the next submit 4001s "session not found" and the sleep/wake recovery's session.resume 4007s the SAME "session not found" because there is no row to resume. That rejection escaped uncaught, surfaced as a raw "Prompt failed / session not found" toast, and dropped the user's message with no way forward. Catch the recovery resume and, when BOTH the live id and the stored row report "session not found" (the never-persisted-draft signature), mint a fresh backend session and land the message there — moving the optimistic message, drift baseline, and attachment sync over to the minted chat. Scoped tightly: a timed-out submit may have actually reached the backend (re-sending elsewhere would double-send), a non-404 resume failure says nothing about whether the stored chat exists (minting would split a real conversation, NousResearch#55578 symptom b), and a background queue drain must never re-home the user's view — all of those keep the existing surface-the-error behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for isolating the distinct no-row path after the earlier resume-success recovery work. Problems
Suggested changes
Automated hermes-sweeper review. |
…submit file.attach / image.attach are session-scoped RPCs and the initial attachment sync runs BEFORE prompt.submit, so a dead first-submit draft with a newly selected attachment failed in staging — ahead of the double-404 recovery, which never got a chance to run. Extract the resume-or-mint recovery into recoverDeadRuntime and invoke it from both session-scoped failure points: the initial attachment sync and prompt.submit itself. A sync-time "session not found" now resumes the stored session (or mints a replacement for the never-persisted-draft double-404 signature), then re-stages the attachments against the live runtime the recovery produced. Behavior at the prompt.submit failure point is unchanged — same guards (timeout double-send, non-404 resume failure, background drains), now routed through the shared helper. Tests: file and image staging regressions covering attach-404 → recovery → re-stage → submit on the minted runtime; a resumed-runtime rebind case (stored row exists → no minting); and the optimistic-state test now asserts the stored-session key passed to updateSessionState, pinning that post-recovery state is keyed under the minted stored id rather than cross-wiring the dead draft's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed both findings in d904d12: Attachment staging before recovery — the recovery is no longer anchored to Regressions added per the review:
Stored-session keying — the optimistic-state test now asserts the stored-session argument passed to
|
Re-triage confirms the reviewer-requested attachment-stage and stored-ID rehoming fixes are now present. Related to open #67539 (narrower) and #67575 (competing double-404 recovery); #67503 additionally covers initial file/image staging and stored-session optimistic-state keying, so this remains open for maintainer consolidation. |
SummaryThree open PRs address #67502's dead first-submit/double-404 path. #67539 adds a narrow resume-error fallback, #67575 adds guarded prompt-submit recovery and re-homing, and #67503 additionally handles pre-submit file/image staging, updates the stored-session state key, and adds regressions for the critical recovery boundaries. Related pull requests
Duplicates#67503, #67539, and #67575 are competing implementations of the same #67502 recovery; #67539 and #67575 can be treated as superseded duplicates of the broader #67503 implementation. Suggested consolidationKeep #67503 open with a salvage path: retain its shared attachment/prompt recovery, fresh stored-ID re-homing, timeout/queue safeguards, and regression coverage for maintainer consolidation. Close #67539 and #67575 as duplicates of #67503 because their visible diffs leave respectively the attachment/timeout/queue gaps and the stale stored-ID/test gaps documented above. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I67502(["issue #67502 (open)"])
subgraph Dup67503 ["PRs duplicating each other"]
P67503["PR #67503 (open)"]
P67539["PR #67539 (open)"]
P67575["PR #67575 (open)"]
end
P67503 -->|best fix| I67502
class I67502 open
class P67503 open
class P67539 open
class P67575 open
class P67503 best
class P67503 target
click I67502 "https://github.com/NousResearch/hermes-agent/issues/67502"
click P67503 "https://github.com/NousResearch/hermes-agent/pull/67503"
click P67539 "https://github.com/NousResearch/hermes-agent/pull/67539"
click P67575 "https://github.com/NousResearch/hermes-agent/pull/67575"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 3 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 37 kB of PR diffs, 9 kB of issue/PR text, 5 kB of discussion (7 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Conflicts in use-prompt-actions/submit.ts, both sides touching the sleep/wake recovery path: - sessionContextDrifted() -> sessionDriftReason() everywhere the branch's shared recoverDeadRuntime helper checked for drift, with upstream's [submit-drift-abort] warn logging. - recoverDeadRuntime's session.resume now goes through resolveSessionProfile + omit_messages (upstream NousResearch#67603), so a recovery can't fork the conversation into another profile's DB. - The recovery retry submits through upstream's submitParams(), so a re-homed send keeps the interrupted/queued flags; submitParams takes an optional text override for the minted-session re-stage. - The minted fallback titles the new chat with bubbleText, matching the primary create path. Test: resume params assertion updated for omit_messages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Superseded by #81261. Thanks for this — the first-submit draft recovery is carried over there, with you credited via Consolidating because this is one bug class across several PRs: |
Fixes #67502
Problem
The first message of a new chat can dead-end with a raw "Prompt failed / session not found" toast, losing the user's text with no way to retry.
A new chat's
state.dbrow is only persisted by the gateway on its first successfulprompt.submit(deliberately lazy, so abandoned drafts never leave "Untitled" rows). If the live session dies before that first submit lands — sleep/wake WS drop followed by the orphan reap, a backend restart, a stale cached runtime id — then:prompt.submitfails with4001 session not found, andsession.resumefails with the same4007 session not found, because there is no row to resume.That second rejection was uncaught in
useSubmitPrompt— it escaped to the outer catch and surfaced verbatim, and every subsequent submit into the draft repeated the loop.Fix
session.resumein try/catch instead of letting it reject the whole submit.createBackendSessionForSendand land the message there — moving the optimistic message, the drift baseline, and the attachment sync over to the minted chat (attachments re-sync so@file:refs resolve in the new session's workspace).optimisticStoredSessionIdfor the optimistic/error-bubble state key so the re-homed send is never keyed under the dead stored id (which would cross-wire the session-state cache).The fallback is scoped tightly; all of the following keep the existing surface-the-error behavior:
targetIsCurrentView()-guarded, so a drain can never re-home the user's current view.Tests
Four new tests in the "sleep/wake session recovery" block:
apps/desktop: full vitest suite green (the one failure,toolset-config-panel.test.tsx, also fails on cleanmainin a full parallel run — pre-existing flake, passes in isolation);tsc --noEmitclean; eslint clean for the touched files.🤖 Generated with Claude Code