Skip to content

fix(gateway): route truncation writes + /undo to the profile database - #91150

Open
glitchbunny0 wants to merge 3 commits into
NousResearch:mainfrom
glitchbunny0:fix/profile-db-truncation-undo
Open

fix(gateway): route truncation writes + /undo to the profile database#91150
glitchbunny0 wants to merge 3 commits into
NousResearch:mainfrom
glitchbunny0:fix/profile-db-truncation-undo

Conversation

@glitchbunny0

Copy link
Copy Markdown

What this fixes

Editing an earlier user message in Desktop on a global-remote profile session (a non-launch profile's chat hosted by the same backend) fails with:

Edit failed
truncate_before_user_ordinal (3) does not match truncate_before_row_id target turn (4)

Root cause chain (from a live incident, logs/gui.log):

  1. Edit submit resolves truncate_before_row_id=1105 correctly — the durable read path (_load_durable_truncation_history) is profile-aware via _session_db(session).
  2. The truncation write then goes through _get_db() — always the launch profile's state.db — and replace_messages INSERTs rows for a session key that has no sessions row in that DB → sqlite3.IntegrityError: FOREIGN KEY constraint failed → error 5008, turn refused.
  3. The desktop's optimistic rewind still updated its local ordinal space, so every subsequent edit retry is refused with the 4030 ordinal mismatch above — the visible symptom.

The fix

This is a re-land of 0640fe7 ("fix(gateway): route truncation writes to profile database", authorship preserved via cherry-pick), which was reverted in 3863de3 purely for scope ("keep profile truncation routing out of scope" of PR #86649) — not because it was wrong. It never got re-landed while the FK bug remained on main.

Plus the sibling of the same class found while fixing:

  • tui_gateway/methods_tools.pycommand.dispatch /undo read the launch-profile DB for remote-profile sessions → always "no user messages to undo" / silent no-op rewind. Now routed through _session_db(session) with the body scoped inside the handle's lifetime (it closes profile handles on exit).

Tests

  • test_prompt_submit_row_id_truncates_profile_owned_history (from the reverted commit; monkeypatches _get_db to fail the test if the launch DB is touched — proves routing, not just outcome)
  • test_cli_undo_routes_profile_owned_session_db (same probe pattern for /undo)
  • Full tests/test_tui_gateway_server.py: 587 passed on this branch.

Verification

Reproduced on a live install (songbird profile hosting, default-profile backend): edit → 5008 FK failure → 4030 on every retry. After the fix, the same edit persisted the truncated transcript into the profile's own state.db and the turn ran.

fangliquanflq and others added 2 commits August 21, 2026 00:53
Same class as the prompt.submit truncation write (re-landed from
0640fe7): a global-remote desktop session owned by a non-launch
profile persists into that profile's state.db, but command.dispatch's
/undo read the launch-profile DB — which has no rows for the session
key — so /undo failed with 'no user messages to undo' and a rewind
would silently no-op against the wrong database. Route through
_session_db(session) like every other session-scoped DB touchpoint.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/sessions Session lifecycle, resume, persistence, history area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 20, 2026
Codex review of the profile-DB truncation routing flagged two follow-ons:

- prompt.submit truncation: _session_db(session) can yield None when a
  profile-owned session's own state.db cannot be opened. The old
  'if db is not None' skip then truncated memory while the profile DB
  kept the old tail — durable zombie history, the exact class this PR
  fixes. Refuse the turn (5008) for profile-owned sessions; memory-only
  sessions (no profile_home, launch db unavailable) keep the legacy
  skip since nothing durable can diverge.

- /undo: a failed post-rewind history reload previously published an
  empty transcript into live memory. The flush-pointer reset would then
  re-append every surviving row, resurrecting the soft-archived turns.
  Keep the pre-rewind in-memory history on reload failure; the DB is
  the source of truth and the next resume/reload converges to it.

Both paths get regression tests (2 new, full file 589 passed).

(cherry picked from commit 9eb1fb8b2ce3a99f0499ee9b9f50303764fef6a0)
@glitchbunny0

Copy link
Copy Markdown
Author

Codex review of this PR flagged two follow-on issues in the new code — both fixed in 9eb1fb8b2c (pushed to this branch):

1. High — truncation could still fail open. _session_db(session) yields None when a profile-owned session's own state.db cannot be opened; the inherited if db is not None skip then truncated memory while the profile DB kept the old tail — recreating the durable zombie-history class this PR exists to kill. Now refuses the turn (5008) for profile-owned sessions. Memory-only sessions keep the legacy skip (nothing durable to diverge from).

2. Medium — /undo wiped live memory on post-rewind reload failure. except: active = [] published an empty transcript after the durable rewind already succeeded; the flush-pointer reset then re-appended every surviving row, resurrecting the soft-archived turns. Now keeps the pre-rewind in-memory history — DB is the source of truth.

Two regression tests added (fail-closed truncation + reload-failure memory preservation). Suite: 589 passed (was 587). Review verdict from Codex: root-cause claim valid, routing fix correct, tests called out as the right kind of regression coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants