You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fixes a stale-model-context race for resumed Desktop/TUI sessions whose durable transcript is advanced by another process, especially cron runs.
A cold resume hydrates session["history"] once. If cron continues appending to the same state.db session afterwards, Desktop can show the newer transcript through its persisted-display refresh while the next prompt.submit still feeds the model the old open-time snapshot.
Fix
Add a pre-dispatch guard for prompt.submit on resumed sessions (resume_session_id).
Wait for the initial deferred resume hydration to settle before comparing state.
Read the model projection from the session's profile-aware owning DB.
Adopt durable history only when it has grown beyond the in-memory snapshot.
Never overwrite an active turn, a shorter/equal durable projection, or a local history mutation that races the DB read.
Sanitize the refreshed replay history through the same path used by resume hydration.
This keeps the change monotonic and avoids letting an external refresh clobber locally-owned turn state.
AI code review — automated review for reference; please use your judgment.
tui_gateway/prompt_history_sync.py:60-77 — ready.wait(timeout=30.0) blocks the calling thread for up to 30 seconds inside prompt.submit dispatch. Why it matters: if handlers run on a shared dispatch loop/thread, one stuck hydration worker makes every prompt across all sessions wait behind the same 30s window, turning a per-session hiccup into a gateway-wide stall; the warning log fires only after the full wait. Suggestion: confirm the dispatch threading model; if shared, lower the bound (a resumed session that hasn't hydrated within ~2-5s is unlikely to finish promptly) or make the timeout configurable and emit the log immediately on entry when ready isn't yet set.
tui_gateway/prompt_history_sync.py:146-147 — The adoption guard is purely length-monotonic (len(durable_history) <= len(start_history)). Why it matters: an external writer that compacts history (summarization, tool-noise pruning) produces a newer durable projection with fewer rows, which will now never be adopted — the stale open-time prefix persists silently, the exact class of staleness this PR targets; notably the fallback read already requests include_row_ids=True, so newer-but-shorter is detectable. Suggestion: adopt when the durable tail's max row id exceeds the snapshot's (or last timestamp is newer) even if shorter, keeping the length rule as the conservative fallback.
tui_gateway/method_ctx.py:51-59 — Wrapping is unconditional on each install(), so a re-install/reload of the method table double-wraps prompt.submit, doubling the durable read (and the lock churn) on every subsequent submit. Why it matters: silent per-submit cost growth that's hard to notice in profiling. Suggestion: tag the wrapper (wrapped._hermes_history_refresh = True) and skip wrapping when the incoming real already carries the marker.
tui_gateway/prompt_history_sync.py:118-141 — Positive: the snapshot-under-lock → read-outside-lock → revalidate(version AND value AND running)-under-lock pattern is textbook and the "legacy in-place mutation forgets to bump version" catch via value comparison is a thoughtful touch. Test gap worth closing: no coverage for the two early-return paths — hydration-timeout (30s) and resume_history_error — which currently guard real behavior only by inspection.
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
area/sessionsSession lifecycle, resume, persistence, historycomp/desktopElectron desktop app (apps/desktop/*)comp/tuiTerminal UI (ui-tui/ + tui_gateway/)P2Medium — degraded but workaround existssweeper:risk-session-stateSweeper risk: may lose/corrupt/mis-associate session or context statetype/bugSomething isn't working
3 participants
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 a stale-model-context race for resumed Desktop/TUI sessions whose durable transcript is advanced by another process, especially cron runs.
A cold resume hydrates
session["history"]once. If cron continues appending to the samestate.dbsession afterwards, Desktop can show the newer transcript through its persisted-display refresh while the nextprompt.submitstill feeds the model the old open-time snapshot.Fix
prompt.submiton resumed sessions (resume_session_id).This keeps the change monotonic and avoids letting an external refresh clobber locally-owned turn state.
Related Issue
Fixes #91508
Regression coverage
tests/tui_gateway/test_prompt_submit_durable_refresh.pycovers:Type of Change
Checklist
main(fcbd1076a93841fa88855acce810e342a5b78101)