Skip to content

fix(chat): session-loss ordinal mismatch + long-session render OOM + version restore - #11

Merged
tall-1997 merged 4 commits into
mainfrom
fix/session-loss-and-crash
Oct 13, 2026
Merged

tall-1997 merged 4 commits into
mainfrom
fix/session-loss-and-crash

Conversation

@tall-1997

Copy link
Copy Markdown
Owner

Root causes

1. Session loss (2026-10-14 incident — 1386 rows lost from one retry tap)

deleteFromMessage / retryFromMessage / truncateBeforeEdit resolved their deleteMessagesAfter cutoff via visible-user ordinal. The two counters disagreed:/n/n- UI side: counts EVERY user-role ChatMessage, including toolResult-only carrier rows that render as nothing

  • DB side: visibleUserCutoff + anchorHeadHasVisibleUserText counts only user rows whose head has a text part

On any session with tool traffic (agent loop persists a synthetic user row per tool_result), ordinal N on the UI side resolved to a DB row dozens of turns early → deleteMessagesAfter archived + deleted the entire session tail.

2. Crash (tombstone_09/12 — RenderThread/SKIA 4.68 GB native in 11 s)

MAX_TAIL_MESSAGES = 4000: the UI tail IS the initial render window. A 1441-message session loaded every row into the UI on open → SKIA exhausted Scudo mappings → abort.

Fixes

Fix Change
Anchor by row id New ChatDao.sortOrderForMessageId; delete/retry/edit anchor by the tapped message DB row id (sourceDbIds covers merged bubbles). visibleUserCutoff / anchorHeadHasVisibleUserText removed.
Bound render window MAX_TAIL_MESSAGES 4000 → 500. Older history stays one tap away via loadOlderMessages turn-aligned paging; DB remains the full transcript.
Recovery ChatRepository.restoreArchivedVersions re-inserts rows lost to the ordinal-mismatch truncation from message_versions (latest version per message, above the current tail, idempotent).

Tests

  • DeleteAnchorByIdTest — pins anchor-selection: user target → own id regardless of toolResult-only rows; assistant target → preceding visible user; blank user rows skipped; merged bubbles via sourceDbIds; cut-point arithmetic per caller.

tall-1997 added 4 commits October 14, 2026 02:24
…load; add version restore

Session loss (2026-10-14 incident, 1386 rows lost from one retry tap):
deleteFromMessage / retryFromMessage / truncateBeforeEdit resolved their
deleteMessagesAfter cutoff via visible-user ordinal, but the two counters
disagreed: the UI side counted every user-role ChatMessage while the DB
walk (visibleUserCutoff + anchorHeadHasVisibleUserText) skipped
toolResult-only user rows. On any session with tool traffic the ordinal
resolved to a row dozens of turns early, and deleteMessagesAfter archived
+ deleted the entire session tail.

Fix: anchor by the tapped message's DB row id (ChatMessage.id is the
MessageEntity.id; sourceDbIds covers merged assistant bubbles) via new
ChatDao.sortOrderForMessageId. visibleUserCutoff /
anchorHeadHasVisibleUserText removed. DeleteAnchorByIdTest pins the
anchor-selection logic.

Crash (tombstone_09/12, RenderThread/SKIA 4.68GB native in 11s):
MAX_TAIL_MESSAGES 4000 -> 500. The UI tail is the initial render window;
at 4000 a 1441-message session loaded every row into the UI on open and
SKIA exhausted Scudo mappings. 500 bounds the worst case; older history
stays one tap away via loadOlderMessages turn paging; the DB remains the
full transcript.

Recovery: ChatRepository.restoreArchivedVersions re-inserts rows lost to
the ordinal-mismatch truncation from message_versions (latest version per
message, above the current tail, idempotent).
@tall-1997
tall-1997 merged commit ce8d340 into main Oct 13, 2026
1 check passed
@tall-1997
tall-1997 deleted the fix/session-loss-and-crash branch October 16, 2026 06:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant