Browser tools: a backgrounded Browser pane silently freezes requestAnimationFrame, making healthy pages look broken
Summary
When the Browser pane is not visible, Chrome backgrounds the renderer and requestAnimationFrame callbacks stop firing. Any page whose mount or content depends on rAF then never finishes rendering — but the browser tools report this as an empty DOM with no errors, which is indistinguishable from a genuine application bug.
The result is that the agent confidently starts debugging code that is completely fine. In my experience this happens several times a day and has cost hours of wall-clock time and a large number of tokens across many sessions. It is the single most expensive false signal I hit with the browser tooling.
Symptom signature
All of these are true at once, which is exactly what a real breakage looks like:
- Every content container has
height: 0and contains only its framework placeholder (for Observable Framework:<!--:cellid:-->). - No error elements anywhere in the DOM.
read_console_messages(includingonlyErrors: true) reports nothing.- Fetching every module and asset by hand returns
200. - Importing those same modules in the page via
javascript_tooland calling them by hand works perfectly and produces correct output. - The dev server logs show the page served and its data loaders completed.
Root cause
document.visibilityState === "hidden" / document.hidden === true for the pane's page. Chrome pauses requestAnimationFrame in backgrounded renderers, so anything scheduled through rAF never runs. This includes framework mount paths and ordinary UI animation code.
Reproduction
preview_start, thennavigateto a page that usesrequestAnimationFrame(Observable Framework pages qualify out of the box).- Do not take a screenshot.
- Inspect with
read_pageorjavascript_tool→ empty containers, zero errors. - Take
computer{action: "screenshot"}→ the renderer wakes and the page resolves within a few seconds.
Step 4 is why "just take a screenshot" appears to randomly fix things: it is the only tool in the set that reliably forces a paint. That accidental workaround masks the real behaviour and makes the bug feel nondeterministic.
Affects both the built-in Browser pane tools and the Claude-in-Chrome tools.
Why it is so costly
The failure mode actively rewards the wrong diagnosis:
- Because nothing errors, the agent concludes the page silently hung and goes looking for a hang in application code.
- Because the same page renders correctly when driven by hand, the agent concludes the framework's cell/mount layer is broken.
- Because it reproduces after a reload, the agent concludes the dev server is wedged and restarts it — which fixes nothing and, for a large site, triggers a multi-minute cache rebuild that makes the next check look broken too, reinforcing the misdiagnosis.
I went through that entire sequence today, including needlessly killing a dev server that had been healthy the whole time.
The one reliable tell is that it reproduces on every page of the site, including trivial pages unrelated to any recent change — but that requires already suspecting the environment, which is the thing the tools give no hint of.
Suggested fixes, roughly in order of value
- Surface it. Have
read_page,get_page_text,find, andjavascript_toolinclude a warning in their result whendocument.visibilityState === "hidden"at call time — e.g. "the page is backgrounded; rAF-driven rendering is paused, take a screenshot or bring the pane to front before concluding the page is broken." This alone would eliminate the misdiagnosis. - Prevent it. Launch the pane's browser with
--disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-renderer-backgrounding, and/or force the lifecycle state via CDPPage.setWebLifecycleState({state: "active"})so a hidden pane still paints. - Wake automatically. Make
navigateand the DOM-reading tools force a frame the wayscreenshotimplicitly does, so inspection is not silently sampling a frozen page.
Any one of the three would fix it; (1) is cheap and would have saved me every one of these hours.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗