Repository navigation
test(desktop): unmount every TerminalSubstrate tree before the window closes - #9440
Conversation
… closes `TerminalSubstrate.test.mjs` only called RTL `cleanup()` in `beforeEach`, so the last test's tree was still mounted when `after()` closed the jsdom window. If ThemeProvider's asynchronous palette update landed after that close, it re-rendered the substrate, the splash effect's palette guard passed, and `requestAnimationFrame` on the closed window started a fresh jsdom interval that nothing cleared. The 60 Hz loop repainted the banner into the test's recording `getContext` stub forever, growing `paintLog` until the heap limit — the file passed all 25 tests in CI and then died ~8 minutes later with "JavaScript heap out of memory". Move the unmount into `afterEach` so every tree, including the last, is torn down before `after(() => dom.window.close())` runs; the `beforeEach` call is now redundant and is removed. Verified: 5 consecutive runs of the file pass 25/25 in ~6.8 s with no act warning. A scratch harness with the same stubs reproduced the mechanism 3/3 without cleanup (palette unresolved at close, +11,556 paintLog entries per 100 ms, one live Timeout, process never exits) and 3/3 with cleanup before close (+0 entries, 0 timers, process exits on its own). Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
kazemek21
left a comment
There was a problem hiding this comment.
Kyle reviewed this PR and the Nexus review-tracker notes.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
jedwards27
left a comment
There was a problem hiding this comment.
🤖 :bot: Jude’s code review agent — team recommendation: APPROVE (GitHub COMMENT event per repository agent-review policy).
Reviewed 3c0adc188206bab8551a7af53dcf6bd54c9d0d09..7f4117340f8c9e4dde06ab25422914c3ed382b86 at a clean matching HEAD. Risk: low — a single Desktop test-file hook change; no product/runtime contract changes. TerminalSubstrate.test.mjs:69–75 moves RTL unmount from the next test’s beforeEach into afterEach, which precedes the file-level after(window.close) even for the final test. The splash effect cancels its scheduled RAF on unmount (TerminalSubstrate.tsx:368–396). No blocking code finding; author action for this review: none.
Verification: At the matching clean head, the focused Node file passed 25/25 and exited normally without its prior act warning; cd desktop && pnpm test passed 6,950/6,950 and 185/185, exit 0; pnpm check and pnpm typecheck exited 0; git diff --check 3c0adc...HEAD and node --check passed. pnpm check emitted three warnings in unchanged files. Live exact-head CI reports successful Desktop JS, smoke, integration, Windows build and macOS build checks. The earlier full-suite attempt with a 120s reviewer cap was superseded by the complete run.
Adversarial evidence and residual risk: Reverting the hook move in a temporary mutation still let the 25 tests pass and exit in one 12.3s run. This demonstrates the old timing-dependent failure is not deterministically caught by the existing file; it does not demonstrate failure of the proposed hook. The PR reports a separate scratch-harness negative/positive control (3/3 each), not an in-tree regression assertion. An explicit deterministic lifecycle assertion would strengthen protection against a future revert, but is optional rather than a current blocker. A single green suite/CI run cannot prove a timing flake permanently absent. No native UI observation is necessary to establish this test-only boundary; the repository’s separate human-testing readiness step remains with the author/human operator before declaring the PR ready.
Verification owner: CI/reviewer for exact-head checks; human author/operator for the repository human-testing checklist. A new head requires delta review.
* origin/main: (24 commits) docs(readme): point desktop readers at block/buzz-app and describe where each client lives (#9493) test(cli,db): freeze the assertion test clock and give pool release tests a test-lane acquire budget (#9442) test(desktop): unmount every TerminalSubstrate tree before the window closes (#9440) fix(mobile): drop the iOS share extension from the build until it's signed (#9489) chore(release): release Buzz Desktop version 0.5.30 (#9491) perf(mobile): parse each event's thread reference once (#9460) perf(mobile): load only the directory when search opens (#9466) perf(mobile): stop rebuilding Home's channel list when a route covers it (#9459) perf(mobile): ask typing subscriptions only for the typing window (#9454) perf(mobile): skip avatar URL parsing for non-HTTP URLs (#9448) perf(mobile): download the identity archive list only when it changed (#9445) perf(mobile): don't treat a Control Center pull as a resume (#9443) fix(mobile): keep a superseded roster read out of the offline snapshot (#9452) perf(mobile): read channel bot roles from the member list (#9451) perf(mobile): read Activity once per reconnect (#9455) fix(mobile): offer role controls for agents (#9475) perf(mobile): stop rebuilding the native nav bar on every scrolled frame (#9458) perf(mobile): keep verified-name batches open as people are added (#9450) perf(mobile): read only changed custom emoji sets on reconnect (#9446) perf(mobile): keep latest-message times on a directory load (#9457) ... Signed-off-by: Will Pfleger <[email protected]> # Conflicts: # crates/buzz-cli/src/desktop_provider.rs
…eaction-recipients * origin/main: (111 commits) perf(acp): avoid repeated observer batch serialization (#9483) docs(readme): point desktop readers at block/buzz-app and describe where each client lives (#9493) test(cli,db): freeze the assertion test clock and give pool release tests a test-lane acquire budget (#9442) test(desktop): unmount every TerminalSubstrate tree before the window closes (#9440) fix(mobile): drop the iOS share extension from the build until it's signed (#9489) chore(release): release Buzz Desktop version 0.5.30 (#9491) perf(mobile): parse each event's thread reference once (#9460) perf(mobile): load only the directory when search opens (#9466) perf(mobile): stop rebuilding Home's channel list when a route covers it (#9459) perf(mobile): ask typing subscriptions only for the typing window (#9454) perf(mobile): skip avatar URL parsing for non-HTTP URLs (#9448) perf(mobile): download the identity archive list only when it changed (#9445) perf(mobile): don't treat a Control Center pull as a resume (#9443) fix(mobile): keep a superseded roster read out of the offline snapshot (#9452) perf(mobile): read channel bot roles from the member list (#9451) perf(mobile): read Activity once per reconnect (#9455) fix(mobile): offer role controls for agents (#9475) perf(mobile): stop rebuilding the native nav bar on every scrolled frame (#9458) perf(mobile): keep verified-name batches open as people are added (#9450) perf(mobile): read only changed custom emoji sets on reconnect (#9446) ... Signed-off-by: Matthew Boston <[email protected]>
Summary
desktop/src/features/terminal/TerminalSubstrate.test.mjscan pass every test and then run the Desktop JS job out of memory several minutes later. This moves the React Testing Library unmount frombeforeEachtoafterEach, so no tree is still mounted when the jsdom window closes. Only the test file changes (6 insertions, 2 deletions).Commit (cherry-picked unchanged from a local flake-fix series):
test(desktop): unmount every TerminalSubstrate tree before the window closesCI sighting
One sighting was found in the CI runs from 7 to 12 November 2026 that were searched:
In that job the suite reported 6,950 of 6,951 passing. The log shows
An update to TerminalSubstrate inside a test was not wrapped in act(...)at 21:54:33Z, thenFATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memoryat 22:02:17Z, and the file is reported as failed at file level (test at src/features/terminal/TerminalSubstrate.test.mjs:1:1) after 504.6 s.Root cause
The file called
cleanup()only inbeforeEach, so the last test's tree was still mounted whenafter(() => dom.window.close())ran. If ThemeProvider's asynchronous palette update landed after that close, it re-rendered the substrate, the splash effect's palette guard passed, andrequestAnimationFrameon the closed window started a fresh jsdom interval that nothing cleared. That 60 Hz loop repainted the banner into the test's recordinggetContextstub indefinitely, andpaintLoggrew until the heap limit. No individual test hangs, so a per-test timeout would not catch it.Fix
afterEach(() => cleanup?.())unmounts every tree, including the last, beforeafter()closes the window. ThebeforeEachcall became redundant and is removed. No assertions change, and no timeouts, retries or sleeps are added.Related issue
N/A (flake fix). No existing issue or PR for this failure was found. Three open PRs touch the same test file (#5057, #5485, #7777); none of them changes where
cleanupruns.Testing
Human testing is still required. This PR does not carry
buzz-review-completed; the author has not yet run this test themselves on this branch.Verification of the fix (before this branch was split out)
actwarning.paintLogentries per 100 ms, kept one live timer, and never exited. With cleanup before the close, 3 of 3 runs added no entries, left no timers, and exited on their own.Local checks on this branch
The push ran the repository's full pre-push hook set from a checkout of this branch's head (
7f4117340), with no lanes excluded and without--no-verify. The branch is one commit behindmain; that commit changes only mobile and docs files, none of them touched here.Ran and passed:
branch-skew,push-head-scopeandfile-size-checkdesktop-check(pnpm check)desktop-typecheck(pnpm typecheck)desktop-test(pnpm test, the full desktop unit suite, which includesTerminalSubstrate.test.mjs)Did not run:
rust-tests,desktop-tauri-checksandmobile-checks. The hook selects lanes by changed path, and this branch changes no Rust, Tauri or mobile file.just ci.A green run does not prove the flake is gone: the failure needs the palette update to land after the window closes.
🤖 Generated with Claude Code