fix: stop re-downloading and re-parsing the full transcript on every watcher tick - #1
Open
sigalor wants to merge 2 commits into
Open
fix: stop re-downloading and re-parsing the full transcript on every watcher tick#1sigalor wants to merge 2 commits into
sigalor wants to merge 2 commits into
Conversation
…watcher tick An actively-working session's transcript grows continuously, and the filesystem watcher fires a `session_upserted` event on every change while it's the currently-viewed session. The client reacted to that by calling refreshFromServer(), which fetched the entire, unbounded message history on every single tick — and the server's getSessionMessages() re-read and re-JSON.parsed the whole JSONL file (plus every referenced subagent agent-*.jsonl file) from scratch each time, regardless of the caller's requested limit. Together this meant network transfer and CPU cost per tick scaled with total session size instead of with how much actually changed, turning a long-running session into a continuous multi-MB refetch loop. - refreshFromServer now requests a bounded tail window (TAIL_REFRESH_LIMIT) and merges only genuinely-new messages by id, falling back to a full fetch only if more messages appeared in one tick than the window covers. - getSessionMessages/parseAgentTools now cache parsed JSONL lines per file (keyed by size+mtime) and read only newly-appended bytes on repeat calls, instead of re-reading and re-parsing from byte 0 every time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mergeTailMessages (introduced in 47828c8) appended not-yet-seen tail messages to the END of the already-loaded array, assuming they were always newer. That's false whenever the loaded page is smaller than the tail window: refreshFromServer's tail request commonly reaches further back than fetchFromServer's initial page (e.g. any session small enough that its whole history fits inside TAIL_REFRESH_LIMIT), so the "new" messages it surfaces are actually OLDER than what's already showing, not newer — they were landing after the newest messages instead of before them. Symptoms in production: messages rendering out of order and reshuffling on every watcher tick ("jumping around"), assistant replies whose content the model itself referenced not appearing where expected, and the chat only looking right immediately after sending a message (the one path that still does a full, correctly-ordered reload). mergeTailMessages now merges by id (tail's copy wins on overlap, so content that updates after the fact — e.g. a tool_result's subagentTools gaining entries — isn't stuck stale) and re-sorts the result chronologically instead of trusting concatenation order. The pure ordering/merge logic (readMessageTime, compareMessagesChronologically, mergeTailMessages) is split into sessionMessageOrdering.ts specifically so it can be covered by a real test — this project has no frontend test runner, and useSessionStore.ts itself can't be imported outside Vite (import.meta.env). The new test reproduces the exact bug shape (a 2-message loaded page vs. a 5-message tail window) and is confirmed to fail against the old concatenation-only logic before this fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
refreshFromServer(client) requested the entire unbounded message history on every filesystem-watcher-triggered refresh; now requests a bounded tail window and merges only new messages by id.getSessionMessages/parseAgentTools(server) re-read and re-parsed the whole JSONL transcript (and every subagent file) from scratch on every call, regardless of the caller's limit; now cache parsed lines per file (keyed by size+mtime) and read only newly-appended bytes.Together these turned an actively-worked, long-running session into a continuous multi-MB full-transcript refetch loop (observed: ~30MB / ~45s per
/messagescall, repeating every couple of seconds) — this fork exists to carry this fix on top of the pinned v1.36.2 release used internally.Test plan
npm run typechecknpm run lintnpm run buildnpx tsx --tsconfig server/tsconfig.json --test server/**/*.test.ts ...— 116/116 passing (115 pre-existing + 1 new test covering the cache's incremental-append, idempotent-repeat, and shrink-fallback behavior)