Repository navigation
Stop the Cleanup dirty sweep from flooding remote hosts - #356
Merged
Merged
Conversation
Fleet view (2.18) feeds App from the merged-backend pipeline, which is keyed on each backend's whole AppState and so reruns on every event — and every output was a fresh array/map each time. The Cleanup modal's dirty-scan effect keyed on that worktree array, so it re-sent a `git status` for every worktree on every streamed token. On a remote host that queued thousands of reads (observed: ~2,000 isDirty requests each taking 7+ minutes, 20s event-loop stalls, 5.4GB RSS), and tab switching and tab creation sat behind the backlog. - merge-backend-states: each output keeps its previous reference unless its contents changed; the whole result does when nothing did. - Cleanup: ask about each path once per open, route each request to the backend that owns the worktree, and load the activity log once. - main: concurrent isDirty requests for the same path share one git call, so an un-updated client can't wedge a server again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Extract the isDirty in-flight dedupe into createSingleFlight with tests for sharing, per-key separation, and clearing on resolve and reject. - Cleanup fetches the activity log once from each backend that owns an eligible worktree, so remote rows age by their own host's history instead of falling back to lastActive. - Pin single-mode merge stability: a hidden backend streaming must leave every output field, and the result itself, unchanged. Co-Authored-By: Claude Opus 5.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.
Hotfix candidate for 2.18.1.
Symptom
In 2.18, switching tabs or creating a tab on a remote takes minutes or looks broken. It shows up in both "All" (Fleet) mode and single-host mode with a remote active.
Root cause
Logs from an affected remote (
agentjesus):harness-serverwas at 335% CPU and 5.4 GB of memory.worktree:isDirtyrequests were sent in one burst. Each took 7+ minutes to answer.Fleet view (#344) feeds App from
getMergedWorktrees. Its cache is keyed on each backend's wholeAppState, which is a new object after every event, so the merge reruns on every streamed token. Every output (flatWorktrees, the owner maps, and so on) came back as a new array or map each time, even when nothing in it had changed.Cleanup.tsx's dirty-scan effect depended on that array. While the modal was open, every token re-sent agit statusfor every worktree, and every deletion made from the modal re-sent the whole sweep again. On the remote, each of those git calls stalls the event loop while it starts, so tab switches and new tabs queued behind thousands of reads. Cleanup also sent every check to the active backend rather than the one hosting each worktree.Fix
merge-backend-states.ts: each merged output keeps its previous reference unless its contents changed, and the whole result does too when nothing did. Every other consumer of these lists also stops re-firing on every token.Cleanup.tsx: each worktree is checked once per open,isDirtyandgetBranchCommitsgo throughbackendFor(path), and the activity log loads once instead of on every update.main/index.ts): concurrentisDirtyrequests for the same path and priority share one git call, so a client still on 2.18 can't overwhelm an updated server.Testing
npm run typecheck,npx electron-vite buildandnpx vitest run(2238 tests) all pass.merge-backend-states.test.tscover reference reuse.perf.logand the code.A remote that is already stuck has to have
harness-serverrestarted to clear its backlog.🤖 Generated with Claude Code