Repository navigation
bug(runtime-host): a transcript page that spans a Turn boundary can exceed the client's 256-message range cap #5183
Description
What happened
Scrolling into history in Maka Desktop fails the fill with
RangeError: Session transcript range exceeds the local capacity limit
at decodeTranscriptPage (packages/runtime-host/src/client/session-subscription.ts:303)
The Main log shows it as a sessions:transcript:load-before failure. The Renderer's edge is then marked spent, so the reader gets no more rows in that direction until they move; scrolling back and forth retries and fails the same way every time. The rows exist and are well-formed — the read is refused by a size check, not by data.
Observed on a real workspace where two adjacent Turns hold 161 and 134 durable messages (295 together, each message small). Deterministic once the reader pages across that boundary.
Not a regression of #5170; main behaves the same. #4428 / #4433 fixed the same limit on the bootstrap path (an oversized target Turn now degrades to null boundaries). This is the page path, which #4433 did not touch.
Where the two limits meet
packages/runtime-host/src/protocol/session-transcript.ts:34-36—SESSION_TRANSCRIPT_PAGE_MAX_MESSAGES = 256, andSESSION_TRANSCRIPT_RANGE_MAX_MESSAGESis the same number.packages/runtime-host/src/server/session-transcript-pager.ts:296-419— after selecting the page by bytes, the Host scans on from the page's far edge to the end of the Turn it landed in and returns that asrangeBoundarySequence, so the client will assemble the whole Turn rather than show a torn one. The budget check at:409-413counts only the records of that extension (retainedMessages), not the rows already in the selected page.packages/runtime-host/src/client/session-subscription.ts:292-304— the client keeps pulling continuations untilrangeBoundarySequenceis in hand, counting every identity across the selected page and the continuations againstSESSION_TRANSCRIPT_RANGE_MAX_MESSAGES, and throws when the total passes 256.
So the Host can promise a boundary the client is forbidden to reach: a 128 KiB page of small messages already holds ~200 rows, and the extension to the Turn's end adds the rest. Hypothesis, not yet verified with a unit test — the numbers in readSessionTranscriptPage need to be traced against a fixture of two adjacent Turns of ~160 and ~130 short messages.
How to reproduce
- A session with two adjacent Turns of roughly 160 and 130 short durable messages (a tool-heavy Turn does this naturally).
- Open the session at the tail; scroll up until the fill has to read across the boundary between them.
- The fill rejects with the
RangeErrorabove; the transcript stops short of the older Turn.
A pager unit test in packages/runtime-host/src/__tests__/session-transcript-pager.test.ts with such a fixture should reproduce it without the app: readSessionTranscriptPage for direction: 'older' with maxBytes: 128 * 1024 anchored inside the 134-row Turn, then assemble to rangeBoundarySequence the way decodeTranscriptPage does.
Expected
Either the Host never names a rangeBoundarySequence the client cannot legally reach (the extension budget must include the selected page, or degrade to a null boundary the way #4433 does for bootstrap), or the client's range cap counts the same thing the Host budgets. Whichever side moves, the two constants must describe one rule.
Changing the constant or the protocol validation at session-transcript.ts:260 is a compatibility-epoch change; a pager-side fix that keeps the wire shape is not.
Environment
- Maka Desktop,
mainat a49ba75 andrefactor/transcript-renderer-window(feat(desktop)!: give the Renderer the transcript window #5170) alike - macOS arm64, local Runtime Host
Activity
Dante-dan commentedon Sep 11, 2026
I reproduced the failure with the 161/134-message fixture using the storage reader's sparse sequence layout and the real pager/client decoder. The range scan already includes the selected page; the failure is in continuation handling: numeric sequence distance overestimates the remaining message count, so a continuation can cross its original Turn boundary and make the client assemble all 295 messages.
PR #5188 keeps each continuation within the boundary encoded in its cursor and resumes the next range at the first deferred message. It preserves message-fragment offsets, normal Turn protection, and the existing protocol constants and wire format. This stays within the Host pager and leaves #5170's Renderer-window design separate.
The regressions exercise both paging directions, owner/shared projections, the exact 256-message limit, and a large deferred message that starts as a partial fragment. The adjacent-Turn and exact-cap cases fail with the reported RangeError without the fix. The pager suite passes 23 tests; the complete Runtime Host suite passes 1849 tests with 12 skipped. Lint, format, build, typecheck, and both desktop/UI knip checks pass.
Feedback or follow-up changes on the branch are welcome.
AI-assisted with OpenAI Codex.
Dante-dan commentedon Sep 11, 2026
take