Pup - Puppeteer Bridge
Public Made by Adomby adom
pup is the AI's own browser: a real, full Chrome on the user's desktop that the AI fully controls (a sandbox, not the user's signed-in browser). Rides Bridge; pup_* verbs open windows and tabs, navigate, screenshot, and eval JS.
browser_set_viewport pins the window for life: survives hard reload, no way to clear, nothing reports it
browser_set_viewport pins the page viewport for the life of the window, survives a hard reload, and there is no way to undo it. The window silently stops responding to OS resizing, and nothing anywhere reports that it is in that state.
This cost a real debugging session: the human resized a pup window, the app did not reflow, and it read as a layout bug in the app we were building. We went hunting through our own resize code. What settled it was the human opening the same URL in their own Chrome, where resizing worked fine.
Repro
Two windows on the same URL, one left alone, one pinned:
adom-desktop browser_open_window '{"sessionId":"vp-control","url":"http://localhost:8901/"}'
adom-desktop browser_open_window '{"sessionId":"vp-pinned","url":"http://localhost:8901/"}'
adom-desktop browser_set_viewport '{"sessionId":"vp-pinned","width":900,"height":600,"deviceScaleFactor":1}'
adom-desktop browser_reload '{"sessionId":"vp-pinned","hard":true}'
Then read innerWidth (what the page believes) against outerWidth (the real OS window) in each:
| Window | innerWidth |
outerWidth |
devicePixelRatio |
|---|---|---|---|
vp-control, never touched |
1673 | 1688 | 1.5 |
vp-pinned, after set_viewport |
900 | 1688 | 1.0 |
vp-pinned, after a hard reload |
900 | 1688 | 1.0 |
The override outlives the document. Resizing the OS window moves outerWidth and leaves innerWidth at 900, so no resize event carries a new width and nothing reflows. Confirmed the page itself is healthy: dispatching window.dispatchEvent(new Event('resize')) by hand reflowed it correctly, because the app's handler reads clientWidth, which is still pinned but at least fires.
Three separate problems
1. No way to clear it. browser_set_viewport declares width and height as required ints, so there is no null, no reset, no "follow the OS window again". Once a window is pinned it stays pinned until it is closed and reopened. Closing is not free either: on this machine a session-scoped browser_close_window took down three windows belonging to other AI threads that shared the Chrome instance, so "just reopen it" can cost someone else their work.
2. Nothing reports the state. browser_status, browser_list_windows and browser_eval all look completely normal on a pinned window. There is no emulatedViewport field, no flag, no hint. A window that ignores resizing is indistinguishable from an app with broken layout code, which is exactly the wrong default when an AI is using pup to verify an app it just wrote.
3. The verb's own hint recommends it without the caveat. From browser_describe:
"Set the page viewport (CDP device-metrics) deterministically, independent of the OS window size — fixes a too-small innerWidth so fullPage/element shots render at a known width."
That is the documented path to deterministic screenshots, so any agent doing visual verification will call it. Nothing says the effect is permanent, or that the human who later looks at that window will find resizing dead. The trap is well-signposted in the direction of walking into it.
Suggested fixes, in priority order
- A reset form. Make
width/heightoptional, or accept{sessionId, reset:true}, and callEmulation.clearDeviceMetricsOverride(puppeteer:page.setViewport(null)). This is the one that unblocks people. - Surface the state. Add
emulatedViewport: {width, height, deviceScaleFactor} | nulltobrowser_statusandbrowser_list_windows. Cheap, and it turns a multi-hour misdiagnosis into a glance. - Amend the hint. Say that the override persists across reloads for the life of the window, that the window will no longer track OS resizing, and name the reset call.
- Optional, nice: have
browser_screenshot_element/fullPageapply the override, take the shot, and clear it, so the common "I just want a deterministic screenshot" case never leaves a window pinned. That would have avoided this entirely, since a screenshot was all we ever wanted.
Environment
- Adom Desktop on Windows, WSL2 container (
ADOM_DESKTOP_MODE=local) - pup driving system Chrome, durable
adom-youprofile - Real display dpr 1.5; the override also forced dpr to the value passed and did not restore it
- Related: #7 shipped
browser_set_viewport; this is a follow-up on its lifecycle, not a regression report