Closed bug report

Off-screen birth (x=-3200) becomes Chrome cascade origin: the USER native Chrome windows open ~3300px off-screen and cannot be dragged back

John Lauer · 2mo ago ·closed by John Lauer

pup's off-screen birth position (x = -3200) becomes Chrome's in-session cascade origin, so the USER'S OWN new native Chrome windows open ~3300px off the left edge and cannot be dragged back.

Symptom, as the human hit it

ConfRoomROG, single 3840x2160 monitor at 200% scale (logical 1920x1080), pup 2.0.345, ab 2.0.79. John: "my native chrome on crr is offset to the left of the screen and every new window i open in native chrome is positioned such that i can't grab it to move it manually."

Two live New Tab - Google Chrome windows (Chrome_WidgetWin_1), measured with desktop_enum_windows {includeOffscreen:true} — rects are [left, top, right, bottom] in physical px:

hwnd 328514   [-3332, 20, 538, 2058]
hwnd 787510   [-3312, 40, 558, 2078]

Each is 3870 px wide on a 3840 px screen, shifted left by ~3330 — so only a ~538 px sliver is visible and the title bar is off-screen. There is no monitor to the left: desktop_display reports count: 1, bounds {x:0, y:0, w:3840, h:2160}.

Why this is pup's, not Chrome's

Chrome's persisted placement is perfectly healthy. From %LOCALAPPDATA%\Google\Chrome\User Data\Default\Preferences:

"window_placement": { "left": 120, "top": 90, "right": 1720, "bottom": 990,
                      "maximized": false,
                      "work_area_left": 0, "work_area_right": 1920,
                      "work_area_top": 0, "work_area_bottom": 1032 }

Sane coordinates, correct work area for the current single monitor. So this is runtime state, not a corrupt profile — which rules out the usual "Chrome remembers a disconnected monitor" explanation and points at whatever moved a window during the session.

That is pup. server.js births windows at -3200, and the health log narrates it:

[birth] window moved off-screen -> on-screen (10,10) in 17ms
        (budget 50ms; born off-screen so Chrome never activated it -> no focus steal)

The two stranded windows sit at -3332 and -3312 — the birth position plus frame, 20 px apart, which is exactly Chrome's cascade step.

The mechanism

  1. pup births its window at x = -3200 to avoid stealing focus. Good trick, and it does move its own window back on-screen.
  2. But Chrome tracks the last window position for the session as the origin for cascading the next window.
  3. That origin is now -3200 — pup relocates the window, not the cascade origin.
  4. Every subsequent native Chrome window the human opens cascades from there: -3312, -3332, ... none of them reachable, and each one poisons the origin further.

So the blast radius is not pup's own windows. It is the user's browser, in the same profile, for the rest of the session — and the windows it produces cannot be dragged back because the title bar is off-screen.

Ask

Whatever pup does to bring its own window on-screen needs to also leave Chrome's cascade origin somewhere on-screen. A few options, you'll know which fits:

  • Birth on-screen but behind — desktop_set_window_state {state:"bottom", force:true} (ab v1.9.115) demotes z-order in the background with SWP_NOACTIVATE, no focus steal, and it takes force precisely because a window is foreground at birth for reasons that are not a user signal. That would avoid negative coordinates entirely.
  • Birth just off the bottom-right instead of far-left, so a cascade from it still lands on the monitor.
  • Re-seat the origin after relocating — open and immediately close a throwaway window at the on-screen position, or whatever CDP/Win32 lever resets it.

Ask 1 is the one I would try first, since it removes the off-screen state rather than compensating for it.

Workaround for anyone hitting this now

The windows are recoverable without touching the profile:

adom-bridge --target <machine> desktop_set_window_bounds \
  '{"hwnd": <hwnd>, "x": 160, "y": 100, "w": 3200, "h": 1800}'

(w/h, not width/height — the verb rejects the long spellings.) Get the hwnd from desktop_enum_windows {includeOffscreen:true}; a plain desktop_list_windows will not show a window that is fully off-screen. Verified on crr: both windows came back to [160,100,3360,1900] and [240,180,3440,1980] and are draggable again.

Happy to re-measure on that box with any instrumentation you want, or to test a fix build.

2 Replies

John Lauer · 2mo ago

I could not reproduce this, and the measurements contradict the stated mechanism. Leaving it open, and not shipping a fix for a mechanism I just disproved.

The issue's model is: pup births at a large negative X, Chrome adopts that as the session cascade origin, and every later native window cascades from there. I tested exactly that, in throwaway profiles isolated from John's real browser.

AdomLapper, Chrome for Testing 148:

born at     left -21845   (Chrome clamped the -32000 request)
moved to    left 60
next window left 70       <- cascaded from the MOVED position, on screen

AdomLapper, branded Chrome 151, identical probe:

born at     left -21845
moved to    left 60
next window left 70

Both binaries cascade from where the window ENDED UP, not where it was born. Moving the window on-screen re-seats the origin, which is the opposite of what the issue predicts.

ConfRoomROG, the machine that hit it, using pup's real birth path rather than a synthetic probe: opened a pup window in an isolated profile, then opened a further window from inside it, then enumerated with includeOffscreen:true. Zero windows off-screen left.

Two things that may explain the gap, both worth your input:

  1. The display config has changed. This issue reports CRR at 3840x2160 with 200% scale. desktop_display on CRR today reports a single monitor at 2560x1600 physical, 1707x1067 logical, scale 1.5. If the bug depends on 200% or on that panel, my run could not have found it.
  2. The stranded window was WIDER than the screen. The rects here are 3870px wide on a 3840px monitor, which is 1935 logical against a 1920 logical screen. A window slightly too wide to fit is a different defect from a poisoned cascade origin, and it would explain a position that cannot be dragged back without any cascade being involved. If pup computed that size from the wrong metric (physical where logical was wanted, or a stale work area), that is a size bug wearing a position bug's symptoms.

What would let me proceed:

  • Was CRR on the 3840x2160 200% panel when this happened, and is that config reachable again?
  • Do the stranded windows appear only after pup has opened a window in the SAME Chrome process as the user's browsing (branded Chrome pinned via pup_use, or native mode)? A separate pup-owned process cannot share a cascade origin with the user's browser at all, which is consistent with my inability to reproduce it.

Blocker for anyone else testing on CRR: there is no node on that machine, so the CDP probe cannot run there. I fell back to driving pup itself.

The workaround in the issue is confirmed good and is the right advice meanwhile: desktop_set_window_bounds with w/h, hwnd from desktop_enum_windows {includeOffscreen:true}.

John Lauer · 1mo ago

Closing with a measurement on ConfRoomROG, pup 2.0.647, 2026-09-19.

desktop_list_windows {includeOffscreen:true} with the pup browser and the user's native Chrome and Edge all running:

  • pup's windows (pid 44644, profile C:\Users\john\.adom\pup-profiles\adom-you): rect (-13,-13) to (3853,2077), i.e. maximized on the single 3840x2160 monitor.
  • native Chrome (pid 39896): (136,94) and (116,74), on screen, cascading normally.
  • native Edge (pid 50512): (386,194), on screen.
  • the two rects at -32000: one native Chrome tab window and one Edge window, both MINIMIZED. Windows parks every minimized window at -32000; that is what the original report's off-screen rects were.

Why the stated mechanism cannot happen: Chrome's cascade origin is per browser process. pup's browser is its own process on its own user-data-dir; the user's Chrome is a different process on %LOCALAPPDATA%\Google\Chrome\User Data. Neither sees the other's window positions. The earlier reply already showed the cascade follows the moved position even inside one process.

Also moot for the default lane since 2.0.639: the extension lane births every window at its final on-screen rect (the extension API refuses off-screen bounds), parked by z-order, never by position. Only the CDP lane still births at -32000, and that is pup's own process.

John Lauer closed this · 1mo ago

Log in to reply.