A streaming answer draws a list number as text until its . arrives, and the agent-view spec fails under load #130
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ui/e2e/agent-view.spec.ts:154("a streaming answer › flows a few characters a frame, formatted as it is written, and settles without moving",@slow) fails withthe answer got shorter at some frame: 268.69 px after 291.08 px. It failed twice in a row in the full lane on 2026-10-02 while #124 and #129 were verified, it fails on main (2ec079a) too, and it passes when run alone.It is the UI, not the measurement
Every frame of a failing run, recorded by a local probe in the spec (the same heights as the lane):
…older than two d…older than two days, a blank line,1…older than two days, thenAddThe answer's source there is a bulleted list ending
- Prune heartbeat rows older than two days, a blank line, then1. Add the timer. When the reveal has written up to the1,repairStreaming(ui/src/lib/markdown.ts) keeps it, so it is drawn as a paragraph one blank line below the list. A frame later it is a list item with no gap above it, and the line jumps up 22 px.repairStreamingalready holds back a last line that is only the start of a block (#,-,1., one or two backticks), but not the digits before the..Walking every prefix of the fixture answer through
StreamingMarkdownin a real browser, a character at a time, finds 7 places where the answer gets shorter, every one a list number drawn as text until its.arrives: the1below the bullets and below "Checks", and2,3,4drawn as an extra line of the item above. The same walk over other shapes finds the same defect twice more:~that will open a~~~fence is drawn, then hidden at~~;| File | Change |with a|under it is two lines, until|-makes it a one-row header, 13 px shorter.Why it fails only under load
The wrong layout is drawn on every run: in 9 of 10 solo runs a frame showed a
2or a4as an extra line of the item above. But the frame after it was the finished item, which is the same height, so the spec cannot see it. The reveal's frames land on nearly the same prefixes every run, locked to the fake's delivery cadence; CPU contention moves them, and sometimes onto the1below the bullets, where the next frame is a line shorter. With seven busy loops, 2 of 8 runs one after another failed, at the same two heights.In production the deltas are token-sized (
1and.are separate tokens, drained at 30 Hz), so the visible text stops on a list number whenever the reveal catches up with the model, for as long as the next token takes.The trace's screencast does not show the frame: under load Chrome left a 217 ms gap in it around the drop. The frames on either side are there.
The
--repeat-each=8 --workers=8reproduction is a different failureNone of its 16 failures (8 in each of the two worktrees) was this assertion. They were 20 s timeouts waiting for the live answer, and "a frame added a block of text". The eight copies share the fake osfd's one scenario clock and restart each other's stream, as the spec's header warns.
Also seen, not fixed here
The parser merges a numbered list that follows a bulleted list into the bulleted one. The fixture's
1. Add the timer,2. Update the queryand3. Update the panelare drawn as-items of the list above, with the blank line between the lists gone. That deserves its own issue.Fix
Hold back a last line that can still become a block's syntax, as
#,-and1.already are: list numbers before their.,~and~~, and a line of pipes, colons and dashes under a line with a pipe. Add a unit test that walks every prefix of an answer, so a case like this fails on every run rather than only on some runs under load.Shipped in
5addb7a, deployed on 2026-10-03 at 01:16 (Actions run 96, manual, together with #132, #133 and #134). Run 95 had been cancelled so osfd would not restart during #124's timing measurement. Archived in0285a3dasopenspec/changes/archive/2026-10-03-streaming-answer-never-shrinks. This change's own session ended after the push, and the #133 session finished the deploy check and the archive.What changed
repairStreamingholds back a last line that is only the start of a block's syntax until the next characters decide what it is. That covers a list marker at any indent (a bullet, or a number with or without its.or)), a heading's#s, and one or two backticks or tildes. It also covers a line of|,:,-and spaces under a line with a pipe, which can still become a table's delimiter row. Inside an unclosed fence nothing is held.StreamingMarkdowndraws them, and requires the rows they take in the terminal register never to fall.agent-transcript's "Streaming text is drawn in its settled form" now says nothing on screen is taken back while text is written, with three new scenarios.Tested
agent-view.spec.ts:154passed 16 of 16 runs under seven busy loops. None of their 7,589 frames ends in a bare list number, bullet or tilde, and none is shorter than the one before. On the old renderer, 23 of 33 runs drew such a frame, and 2 of them failed.Verified on production
/api/statusreports7b6a101, which contains5addb7a./healthzhas a new boot_id,boot_01M4031RD5W1RT9B98G2MNF1EE.index-DPU8Rmow.jsholds both new rules,BLOCK_STARTandDELIMITER_START, verbatim.Left over
|arrives, and then joins the table, 13 px shorter.