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, and SESSION_TRANSCRIPT_RANGE_MAX_MESSAGES is 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 as rangeBoundarySequence, so the client will assemble the whole Turn rather than show a torn one. The budget check at :409-413 counts 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 until rangeBoundarySequence is in hand, counting every identity across the selected page and the continuations against SESSION_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
RangeError above; 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
What happened
Scrolling into history in Maka Desktop fails the fill with
The Main log shows it as a
sessions:transcript:load-beforefailure. 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;
mainbehaves 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
readSessionTranscriptPageneed to be traced against a fixture of two adjacent Turns of ~160 and ~130 short messages.How to reproduce
RangeErrorabove; the transcript stops short of the older Turn.A pager unit test in
packages/runtime-host/src/__tests__/session-transcript-pager.test.tswith such a fixture should reproduce it without the app:readSessionTranscriptPagefordirection: 'older'withmaxBytes: 128 * 1024anchored inside the 134-row Turn, then assemble torangeBoundarySequencethe waydecodeTranscriptPagedoes.Expected
Either the Host never names a
rangeBoundarySequencethe 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:260is a compatibility-epoch change; a pager-side fix that keeps the wire shape is not.Environment
mainat a49ba75 andrefactor/transcript-renderer-window(feat(desktop)!: give the Renderer the transcript window #5170) alike