Skip to content

fix(ui): stop reloads from retaining every previous review - #1201

Merged
benvinegar merged 1 commit into
modem-dev:mainfrom
rnihesh:fix/watch-reload-leak
Oct 18, 2026
Merged

benvinegar merged 1 commit into
modem-dev:mainfrom
rnihesh:fix/watch-reload-leak

Conversation

@rnihesh

@rnihesh rnihesh commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Running hunk diff --watch for a while, memory keeps climbing and never comes back down. I had two instances open on this repo and noticed it.

What's happening

On a reload, useTerminalReview rebuilds resolveCommentRequest in the render where the files change, then rebuilds currentReviewVerticalStop one render later when the measured stops catch up. Closures from one render share its scope, so each of these keeps the previous render alive through the other one. The chain goes all the way back to the first load, so every reload's DiffFiles, row plans and section geometry stick around.

I found it by toggling one file between two states under --watch and diffing heap snapshots (Bun.generateHeapSnapshot) after a forced GC. With 120 reloads there were 284 live useTerminalReview render scopes and ~880 DiffFiles for a 6-file diff.

Fix

currentReviewVerticalStop reads the stops from a ref (same pattern the hook already uses for lineCursorsRef / visibleFilesRef), so it no longer depends on reviewVerticalStops and the chain never forms.

Same watch run before/after, compiled binary, forced GC every 10s:

before after
live objects, 242 reloads 248k → 1.13M ~250k flat
JS heap 177 → 239 MB ~184 MB flat
retained render scopes (120 reloads) 284 6

RSS still goes up and down with the allocator, but it drops back now instead of only going up.

Tests

Added a test to useTerminalReview.test.tsx that does 12 reloads, holds a WeakRef to each reload's file, and checks the early ones are collected after Bun.gc(true). It fails on main (8/8 retained) and passes with the fix.

  • bun test packages/hunk/src/ui/hooks/useTerminalReview.test.tsx (64 pass)
  • bun run typecheck, bun run lint, oxfmt --check
  • bun run test: 5093 pass, 8 fail. The same 8 fail on main for me too: bun test packages/hunk/src/ui/AppHost.reload.test.tsx packages/hunk/src/extensions/hostRuntimeModules.test.ts packages/hunk/src/extensions/installedPackageResolution.test.ts gives 28 pass / 8 fail with and without this change.
  • Didn't run the PTY / TTY smoke suites since this doesn't change rendering or interaction.

Tested on macOS (arm64), Bun 1.4.3.

Related

No existing issue for this one that I could find. #521 trimmed what each section geometry holds; this is about old geometries not being released at all.

Not in this PR

Other useCallbacks in this hook could form the same kind of chain if their deps change in staggered renders. After this fix the snapshots showed nothing else growing on reload, so I left them alone.

currentReviewVerticalStop was rebuilt whenever the measured stops changed,
which happens one render after a reload swaps the files. That render's scope
still held the resolveCommentRequest built for the new files, whose scope held
the previous currentReviewVerticalStop, and so on back to the first load. In
--watch every reload kept the old DiffFiles and row plans alive.

Read the stops through a ref so the callback stays stable and the chain never
forms.
@vercel

vercel Bot commented Oct 18, 2026

Copy link
Copy Markdown

@rnihesh is attempting to deploy a commit to the Modem Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

@rnihesh

rnihesh commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

@benvinegar would you mind taking a look when you get a chance? Ran into this with a couple of --watch sessions left open for a while. It's a small change, but let me know if you'd rather fix it differently.

@benvinegar

Copy link
Copy Markdown
Member

@rnihesh Which version were you running?

@benvinegar
benvinegar merged commit 1c9ae6b into modem-dev:main Oct 18, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants