fix: gateway sync race condition + hybrid session data loss — v0.50.93 (PR #714)
Fixes and extends PR #676 (yunyunyunyun-yun). Race guard in sessions.js SSE handler; prefix-equality check in routes.py _handle_session_import_cli. Closes #676. Co-authored-by: yunyunyunyun-yun <yunyunyunyun-yun@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Hermes Web UI -- Changelog
|
# Hermes Web UI -- Changelog
|
||||||
|
|
||||||
|
## [v0.50.93] — 2026-04-19
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Gateway message sync no longer corrupts the active session on slow networks** — the `sessions_changed` SSE handler now captures the active session ID before the async `import_cli` fetch and validates it in `.then()`, preventing session-switch races from overwriting the wrong conversation. Added `is_cli_session` guard so the handler only fires for CLI-originated sessions. The backend import path now also verifies that existing messages are a strict prefix of the fresh CLI messages before overwriting, preventing silent data loss on hybrid WebUI+CLI sessions. (PR #676 by @yunyunyunyun-yun)
|
||||||
|
|
||||||
## [v0.50.91] — 2026-04-19
|
## [v0.50.91] — 2026-04-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -2869,9 +2869,17 @@ def _handle_session_import_cli(handler, body):
|
|||||||
|
|
||||||
sid = str(body["session_id"])
|
sid = str(body["session_id"])
|
||||||
|
|
||||||
# Check if already imported — idempotent
|
# Check if already imported — refresh messages from CLI store if new ones arrived
|
||||||
existing = Session.load(sid)
|
existing = Session.load(sid)
|
||||||
if existing:
|
if existing:
|
||||||
|
fresh_msgs = get_cli_session_messages(sid)
|
||||||
|
if fresh_msgs and len(fresh_msgs) > len(existing.messages):
|
||||||
|
# Prefix-equality guard: only extend if existing messages are a prefix of
|
||||||
|
# the fresh CLI messages. Prevents silently dropping WebUI-added messages
|
||||||
|
# on hybrid sessions (user sent messages via WebUI while CLI continued).
|
||||||
|
if existing.messages == fresh_msgs[:len(existing.messages)]:
|
||||||
|
existing.messages = fresh_msgs
|
||||||
|
existing.save(touch_updated_at=False)
|
||||||
return j(
|
return j(
|
||||||
handler,
|
handler,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -331,6 +331,30 @@ function startGatewaySSE(){
|
|||||||
const data = JSON.parse(ev.data);
|
const data = JSON.parse(ev.data);
|
||||||
if(data.sessions){
|
if(data.sessions){
|
||||||
renderSessionList(); // re-fetch and re-render
|
renderSessionList(); // re-fetch and re-render
|
||||||
|
// If the active session received new gateway messages, refresh the conversation view.
|
||||||
|
// S.busy check prevents stomping on an in-progress WebUI response.
|
||||||
|
// is_cli_session check ensures we only poll import_cli for CLI-originated sessions.
|
||||||
|
if(S.session && !S.busy && S.session.is_cli_session){
|
||||||
|
const changedIds = new Set((data.sessions||[]).map(s=>s.session_id));
|
||||||
|
if(changedIds.has(S.session.session_id)){
|
||||||
|
// Capture active session ID before async fetch — race guard.
|
||||||
|
// If the user switches sessions while the fetch is in-flight, discard the result.
|
||||||
|
const activeSid = S.session.session_id;
|
||||||
|
api('/api/session/import_cli',{method:'POST',body:JSON.stringify({session_id:activeSid})})
|
||||||
|
.then(res=>{
|
||||||
|
if(!S.session || S.session.session_id !== activeSid) return;
|
||||||
|
if(res && res.session && Array.isArray(res.session.messages)){
|
||||||
|
const prev = S.messages.length;
|
||||||
|
S.messages = res.session.messages.filter(m=>m&&m.role);
|
||||||
|
if(S.messages.length !== prev){
|
||||||
|
renderMessages();
|
||||||
|
if(typeof highlightCode==='function') highlightCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(()=>{ /* ignore — next poll will retry */ });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}catch(e){ /* ignore parse errors */ }
|
}catch(e){ /* ignore parse errors */ }
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user