Where a blockfile:read_range call spent its time. Two pane opens waited
about 17 s on this call while the log recorded only when it started
(docs/reports/REPORT_AGENT_OPEN_STALL_RCA_2026_10_05.md); with
filestore: connection held a long time, this names the step that waited.
A non-circular output this large is never read whole: loading it holds the
store’s connection mutex for the whole read (about 15 s for a 1.3 GB
transcript), stalling every agent’s appends and every pane’s reads in this
process (docs/reports/REPORT_AGENT_OPEN_STALL_RCA_2026_10_05.md). When the
index can’t serve such a read, read_range answers with an error instead;
the pane falls back to its short tail read, which extends the index again.
A transcript stream’s line count from its counter, with the stream and
generation it counts (Phase 5a-3). A file with no epoch yet gets one
first — init_line_counter, one read of the file, once per epoch; call on
the blocking pool. None when it can’t be counted (no such file, or
mid-write by a build without transactions), and the caller falls back.
Whether line starts a turn in a Claude stream-json transcript: the same
rule as the frontend’s ClaudeTranslator.handleUserMessage producing a
user_message — a user record whose content is a non-empty string, or
an array with an image and some text and no tool_result (a text-only
array is how the CLI records “[Request interrupted by user]” and other
meta lines, never rendered as a turn).
blockfile:read_range, also called in-process: the viewer feed reads its
snapshot through it (server::http_viewer), so the feed gets the same
bounded read the pane does and never a whole-file one.
Where block_id’s transcript stands, from the same source read_range
reads it from (the block’s own output, or the agent’s global zone): its
stream, generation and line count. None while it doesn’t exist or can’t
be counted. For the viewer feed, which reads the tail through read_range.
tail_bytes: keep the newest lines that fit in max_bytes (the newest
line always stays, however large; a line costs its length plus its
newline), then move the start up to the first turn start among them, if
there is one, so the pane doesn’t open on the tail of a turn. Lines before
first_line were already trimmed (tail_turns): result.offset wins.
Whether read_range may load this file whole (its fallback when
output.idx can’t serve a read). Everything but a large, non-circular
transcript may; terminal ring buffers (circular) stay bounded by their
maxsize.