Chasing "Minesweeper is laggy" ended somewhere else entirely. Press-to-paint is 29ms median at x11vnc's socket and 29ms through the sidecar relay, but 35-200ms+ in the browser — so every bit of the variance is added by the one hop between, which is officer. Officer answering a trivial 401 on /api/health: median 1ms, p90 2ms, max 490ms. summarizeTranscript full-reads and JSON.parses an entire transcript to build a session-list row; timed against the largest here (196 MB, 70,137 entries) that is 323ms to read plus 541ms to parse — 864ms of blocked loop per call. It is cached on mtime, and a LIVE session appends every few seconds, so the cache misses exactly when the agent is busy. Filed rather than fixed: it is a hot path shared by every surface, and the right shape (incremental parse keyed on size, versus moving summaries off the request path) is an architecture call. The file already contains the argument for the incremental version — "Claude only ever appends". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: f7a62308-0977-4cf4-aa29-c020853fd420
3.0 KiB
id, title, theme, status, created, date_source, order
| id | title | theme | status | created | date_source | order |
|---|---|---|---|---|---|---|
| known-bugs/officer-blocks-its-event-loop-parsing-transcripts | Officer blocks its own event loop for ~0.9s re-parsing a live transcript, and everything it serves stalls with it | known-bugs | open | 2026-08-29 | stated | 7 |
Found 2026-08-29 while chasing "Minesweeper is laggy today". It is not a Minesweeper bug. The game's display is relayed through officer, and officer periodically stops answering anything for up to a second.
The measurements, in order. Press-to-paint at x11vnc's own socket: median 29ms, max 35ms, stable
even with a 1.5s idle gap before each click. Through the sidecar's own /ws relay: median 29ms, max
43ms — so the sidecar adds nothing. In the browser, where it is actually felt: 35ms to 200ms+, varying
per click. All of the variance is introduced between the sidecar and the browser, and the only thing
there is officer.
Officer answering a trivial unauthenticated 401 on /api/health, 30 samples: median 1ms, p90 2ms, max
490ms. The loop is fine until it is blocked outright.
What blocks it. summarizeTranscript (api/chat/claude-sessions.ts) full-reads and JSON.parses a
whole transcript to build a session-list row. Timed against the largest on this machine — 196 MB, 70,137
entries: readFileSync 323ms, split + parse every line 541ms, 864ms blocking, per call.
It is cached, and the cache cannot help. The key is mtime, and the comment above it says why that was
believed to be exact: "Claude only ever appends". But a live session appends every few seconds, so
its mtime moves, the entry misses, and the whole 196 MB is re-read and re-parsed — for a row in a list.
The busier the agent, the more often. 468 MB across 174 transcripts here, and the biggest one belongs to
a commander that has been running all day.
Why it reads as a Minesweeper regression. Nothing in the plugin changed (last commit two days ago), and the same cost was invisible when that transcript was 10 MB and this took ~45ms. It scales with transcript size, so it arrives gradually and then all at once.
Scope is much wider than the game. Every HTTP request, every chat socket frame and every relayed plugin websocket shares that loop. The game is just the one surface where a 500ms stall is impossible to miss.
The fix the file argues for itself. "Claude only ever appends" is exactly what makes incremental
summarisation correct: keep the running summary keyed on (path, size) and consume only the bytes appended
since last time, instead of re-reading from zero. Turns ~864ms into the cost of the new tail. listClaudePwds
already does a bounded head read for cwd (claude-sessions.ts, "a bounded head read"), so the precedent
for not reading the whole file is in the same module.
Not attempted here: it is a hot path shared by every surface, and the right shape — incremental parse versus moving summaries out of the request path entirely — is an architecture call rather than a patch.