fix(desktop): don't drop a new chat's first send when it carries an attachment - #66539
fix(desktop): don't drop a new chat's first send when it carries an attachment#66539brian717 wants to merge 1 commit into
Conversation
…ttachment createBackendSessionForSend re-homes the session refs and route to the chat it mints, but its navigate() only lands on the next React render. When the submit then awaits an image/file upload, that late re-home flips the route token mid-flight and sessionContextDrifted() misreads the app's own re-home as a user session switch — silently aborting the send: the optimistic message is dropped, no prompt.submit reaches the gateway, no DB row is written, and the route 404s. Text-only sends win the race because nothing real is awaited between the re-pin and prompt.submit. Once this submit has minted the session, judge drift by the created session id (which every real switch retargets synchronously) instead of the deferred route token. A genuine switch away during the upload is still detected and still aborts. Fixes NousResearch#65733
Related to #65733 and competing with open #64759: both prevent an app-owned post-create route commit from being treated as a user switch, but this branch validates the created session refs after attachment upload while #64759 uses a broader binding approach. Maintainer choice. |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused reproduction and regression coverage. The delayed self-navigation race is present on current main: apps/desktop/src/app/session/hooks/use-session-actions/index.ts:307-320 re-homes refs then calls navigate(), while submit.ts:431-443 snapshots the route token, awaits attachment sync, and aborts on the resulting token change.
Problems
apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts:194removes route-token validation for the entire post-create submit window. That also accepts unrelated route-only navigation:openSettings()callsnavigate(SETTINGS_ROUTE)atapps/desktop/src/app/session/hooks/use-session-actions/index.ts:416-418without changing either session ref, so this branch can still dispatchprompt.submitafter the user has left chat.
Suggested changes
- Preserve route validation with an explicit created runtime/stored/route binding. Accept only the expected deferred transition to the created route, and add a Settings-route-during-upload regression test.
This is an automated hermes-sweeper review.
| (createdSessionId !== null | ||
| ? activeSessionIdRef.current !== createdSessionId || | ||
| selectedStoredSessionIdRef.current !== startingStoredSessionId | ||
| : selectedStoredSessionIdRef.current !== startingStoredSessionId || getRouteToken() !== startingRouteToken) |
There was a problem hiding this comment.
This branch ignores route changes for the rest of the post-create submit window. openSettings() navigates to SETTINGS_ROUTE without changing either session ref (use-session-actions/index.ts:416-418), so an attachment upload completing after that navigation would still submit. Preserve the route check by accepting only the expected created-session route transition.
SummaryThree PRs address #65733’s late route-flush race: #66185 adds a post-create routed-session identity check, #66539 relies on created-session refs, and #67812 globally replaces raw route-token drift with stored-session identity. Related pull requests
Duplicates#66185, #66539, and #67812 target the same #65733 route-flush race; #66185 and #66539 are the closest duplicates because both scope alternate drift semantics to the post-create window, while #67812 applies identity semantics globally. Suggested consolidationClose #66185 and #66539 as already implemented on main by 1bdd478, based on the automated verdict’s cited shared guard at apps/desktop/src/app/session/hooks/session-context-drift.ts:93-115, its submit integration at apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts:206 and :506, and regression coverage at apps/desktop/src/app/session/hooks/session-context-drift.test.ts:102-112. Keep #67812 closed; the three PRs can be consolidated as superseded alternatives, with #66185’s semantic route-binding tests representing the strongest salvageable reference from the duplicate set. 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
I65733(["issue #65733 (open)"])
subgraph Dup66185 ["PRs duplicating each other"]
P66185["PR #66185 (open)"]
P66539["PR #66539 (open)"]
P67812["PR #67812 (closed)"]
end
P66539 -.->|partial| I65733
class I65733 open
class P66185 open
class P66539 open
class P67812 closed
class P66185 best
class P66539 target
click I65733 "https://github.com/NousResearch/hermes-agent/issues/65733"
click P66185 "https://github.com/NousResearch/hermes-agent/pull/66185"
click P66539 "https://github.com/NousResearch/hermes-agent/pull/66539"
click P67812 "https://github.com/NousResearch/hermes-agent/pull/67812"
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: 30 kB of PR diffs, 16 kB of issue/PR text, 7 kB of discussion (8 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
What does this PR do?
Fixes the first send of a brand-new chat being silently dropped when it carries an attachment (image or file) in remote-gateway mode. The optimistic message disappears, the composer bounces back, no
prompt.submitreaches the gateway, and the freshly-minted route ends up 404-ing, with no error surfaced. Text-only sends are unaffected, which is what made this present as "new chats with images just don't start."The root cause is a race in the submit drift guard. On a new chat,
createBackendSessionForSend()re-homes the session refs and callsnavigate()to the chat it just created, but the react-router location (and therouteTokenthe guard reads) only updates on the next render. The pipeline re-pins its drift baseline immediately after create, so it captures the pre-create route token. It then awaits the real attachment upload (image.attach_bytes/file.attach), during which React re-renders and the route token flips to the new session. The post-uploadsessionContextDrifted()check compares against the stale token, misreads our own re-home as a user session switch, and takes the silentabortForSessionSwitch()path.The fix: once a submit has minted its own session, judge drift by the created session id instead of the deferred route token. Every real session switch retargets
activeSessionIdRef/selectedStoredSessionIdRefsynchronously, so a genuine switch away during the upload is still detected and still aborts; we only stop treating our own latenavigate()as drift. Paths that don't create a session are unchanged and keep using the route token.Related Issue
Fixes #65733
Type of Change
Changes Made
apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts: track acreatedSessionIdfor the post-create window and makesessionContextDrifted()compare against it (via the synchronously-retargeted session refs) instead of the route token once this submit has created the session.apps/desktop/src/app/session/hooks/use-prompt-actions/index.test.tsx: added two regression tests: a new chat + image in remote mode where the route re-home lands mid-upload (submit must still reach the gateway with the created session id), and a genuine mid-upload session switch (must still abort, so the guard isn't blinded).How to Test
cd apps/desktop && npx vitest run --project ui src/app/session/hooks/use-prompt-actions/index.test.tsxnew-chat attachment drift (#65733)pass; the full file is green (54/54).submit.tsand the first new test fails with the submit resolvingfalse(the dropped send). Restore it and it passes.Manual repro (remote gateway): open Desktop, start a new chat, attach an image, send - before the fix the send is swallowed; after, it goes through and streams a response.
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass - N/A (desktop/TypeScript change; verified with the desktopvitestsuite instead)Documentation & Housekeeping
docs/, docstrings) - or N/Acli-config.yaml.exampleif I added/changed config keys - or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows - or N/A