Repository navigation
Description
Description
Description
On a dual-monitor Windows setup, OpenCode Desktop intermittently enters a state where an invisible window (an app-owned Chrome_WidgetWin_1 window — either the main BrowserWindow with stale bounds or a transparent overlay BrowserWindow) sits on top of other applications and swallows all mouse input. Affected windows stay fully visible but cannot be clicked ("behind glass" feeling). In the worst observed episode, no window on the entire desktop could be interacted with — GetForegroundWindow() returned NULL, i.e. Windows input/foreground routing was wedged system-wide.
Only recoveries that work: lock screen + log back in, killing the OpenCode process, or restarting the graphics driver (Win+Ctrl+Shift+B).
Note: this was observed on an enterprise-customized build of OpenCode Desktop 2.1.4 (custom updater/config; the desktop shell itself appears unmodified). The window-management path involved is upstream packages/desktop, so I believe this is worth reporting here.
Environment
- OpenCode Desktop 2.1.4 (Electron), bundled opencode-cli 2.1.4
- Windows 11 Pro 23H2, build 10.0.22631
- Two monitors; secondary monitor arranged above the primary → negative Y coordinates (OpenCode main window lives at
(-128,-1440), size 3440x1440)
Steps to reproduce (probabilistic)
- Run OpenCode Desktop on a multi-monitor setup where one monitor has negative coordinates / mixed DPI scale.
- Use it across display-state transitions: lock screen / monitors sleep+wake / scale changes. Episodes occurred roughly daily.
- At some point after such a transition (suspiciously often right after a task-completion toast appears), other windows become visible-but-unclickable. Sometimes it escalates to the whole desktop being unclickable.
Evidence
Captured with a background watcher sampling WindowFromPoint() at the cursor every 2 s, comparing the top window's owner PID against the foreground window's PID.
During a freeze:
[2026-10-26 10:04:00] GLASS-SIGNATURE cursor=(576,-618)
TOP: class=Chrome_WidgetWin_1 proc=OpenCode(pid 19172) title='OpenCode'
rect=(-112,-1162)-(2661,11)
FG: proc=Idle(pid 0) style=HIDDEN <- NO foreground window exists
[2026-10-26 10:04:11] TOP: same OpenCode window
FG: class=XamlExplorerHostIslandWindow proc=explorer title='任务切换' <- user pressing Alt+Tab, clicks still eaten
Key observations:
- The topmost window under the cursor during every freeze is OpenCode's own
Chrome_WidgetWin_1window (main BrowserWindow with stale bounds, or a transparent overlay BrowserWindow — they share class/title in the capture). - The bounds during the freeze are a stale DPI-scaled rectangle: 2773x1173 ≈ 81% of the window's normal size (3440x1440) — i.e. laid out with a different DPI scale than the monitor's current one. Chromium apparently did not re-run placement / hit-test updates after the display-scale change.
GetForegroundWindow() == NULLduring the total-freeze episode indicates wedged input/foreground routing, not merely a z-order issue.- Not the IME: Sogou IME injects
SoBS_*layered topmost windows into the OpenCode process and was initially suspected, but the capture shows the blocker isChrome_WidgetWin_1; switching IME does not affect recovery. - Not a crash: Crashpad has 0 reports, no GPU errors in logs. (Separately: 179
ResizeObserver loop completed with undelivered notifications.renderer errors in one session — consistent with layout thrash around DPI transitions.)
Suspected trigger (hypothesis, A/B test pending)
Episodes appear to coincide with task-completion toast notifications (bottom-right). Hypothesis: the transparent overlay BrowserWindow used for toasts shows/hides across a DPI/display transition, ends up with stale bounds, and stays invisible-but-hit-testable = full-screen invisible input eater. Disabling completion notifications is the pending experiment.
Expected behavior
Windows/overlays belonging to OpenCode must never intercept mouse input while invisible, and window bounds must be re-asserted after DPI/display changes.
Workarounds found
| Action | Result |
|---|---|
| Lock screen → log back in | Always recovers (resets display/input state) |
Kill OpenCode.exe |
Always recovers |
Win+Ctrl+Shift+B (restart graphics driver) |
Recovers, fastest |
| Switch IME / click elsewhere | No effect |
Suggestions
- Re-assert window bounds and
setIgnoreMouseEventsstate onWM_DPICHANGED,WM_DISPLAYCHANGE, and session-unlock events. - Fully destroy toast/overlay BrowserWindows on dismissal (or mark them click-through whenever not actively showing), never leave them hidden at alpha 0 but hit-testable.
- Handle windows on negative-coordinate monitors in the window-placement path.
Happy to provide the full watcher script, complete logs, or additional captures on request.
Plugins
No response
OpenCode version
OpenCode Desktop 2.1.4 (Electron), bundled opencode-cli 2.1.4
Steps to reproduce
No response
Screenshot
No response
Operating System
win11
Terminal
win terminal
Activity
opencode-agent commentedon Oct 26, 2026
Thanks for the detailed capture, it helps a lot.
A few things would help us narrow this down before we try to reproduce it:
OpenCodewindow, so if the custom build adds its own overlay or notification window, that could be the cause.You can reply here with the details, or edit the issue. If you edit it, please also leave a comment saying you've updated it so we know when you're done.
I didn't find any existing issue that reports this same problem.
ouc3www commentedon Oct 26, 2026
Answers to your questions:
2. Monitors (hard data):
OpenCode's main window is maximized on the secondary 125% monitor.
4. Window state + triggering events: the main window is maximized on the secondary monitor. Every episode we've captured followed a display-state transition: workstation lock/unlock or monitor sleep/wake. Never spontaneously.
New smoking gun (exact arithmetic match): the culprit window rect captured during the freeze,
(-112,-1162)-(2661,11)(2773x1173), is exactly the secondary monitor's DPI-virtualized logical bounds(-102,-1152)2752x1152 (= 3440x1440 / 1.25) plus standard window border padding (-10px top-left, +21px bottom-right):So at freeze time the maximized window had been re-laid-out using the monitor's DPI-virtualized (logical) coordinates as if they were physical pixels, covering the entire secondary monitor. Combined with the wedged foreground state (
GetForegroundWindow() == NULL), this looks like a mixed-DPI (200% vs 125%) coordinate confusion in the Electron/Chromium window placement path, triggered by lock/unlock or monitor wake while the window is maximized on the lower-DPI monitor.1. Official build: not tested yet — I'll install the official OpenCode Desktop 2.1.4+ side-by-side and report whether it reproduces.
3. Notifications test: in progress. One note: our enterprise build renders completion toasts in-app, so I'm also testing with notifications disabled entirely. Will follow up here with the result.
I've also automated evidence capture: a watcher samples
WindowFromPoint()at the cursor every 2 s and logs any state where the top window under the cursor belongs to OpenCode while the foreground window belongs to another process. Happy to attach the script and full logs if useful.ouc3www commentedon Oct 26, 2026
Second episode captured by the background watcher, ~40 minutes after the first — reproducible and deterministic:
Episode 2 timeline (2026-10-26, local time):
Key points:
(-112,-1162)-(2661,11)— exactly the secondary monitor's DPI-virtualized logical bounds ((-102,-1152)2752x1152 = 3440x1440 / 1.25) plus window border padding. This is not a random misplacement; it is the same deterministic wrong re-layout every time: the maximized-on-125%-monitor window gets re-laid-out using logical coordinates as physical pixels, covering the whole monitor as an invisible input eater.Will report back with the official-build test and the notifications-off test as promised.
ouc3www commentedon Oct 26, 2026
Correction / answer to Q1: the build in question is the official OpenCode Desktop 2.1.4 — not a modified binary. electron-updater in this install points to the official feed
https://opencode.ai/update/api/latest/desktop/opencode/, and itslatest.yml(fetched today) lists 2.1.4 (releaseDate 2026-10-23) as the current version, matching the installedOpenCode.exeFileVersion 2.1.4.0. The only "enterprise customization" is at the config layer (opencode.jsonwith internal model providers); the app shell is stock.So the side-by-side test is moot: this happens on the official build, current version.
That also makes your note the key insight: since upstream Desktop doesn't create a transparent toast/overlay window, the
Chrome_WidgetWin_1captured in both freezes is most likely the main BrowserWindow itself — maximized on the 125% secondary monitor — whose bounds/hit-test got re-laid-out using the monitor's DPI-virtualized logical coordinates (exact arithmetic match documented above) after a lock/unlock or monitor-wake transition. I'm downgrading the notification-trigger hypothesis accordingly; the display-state transition remains the common factor in every captured episode, and the ResizeObserver error storm in renderer.log immediately precedes each freeze.Still to come: the notifications-off test (in progress, though given the above it may well be independent of notifications).
ouc3www commentedon Oct 26, 2026
Answer to Q3 (notifications-off test): still reproduces with notifications disabled.
Third episode captured at 10:50:46-10:50:56 local time, with completion notifications turned off:
So the experiment matrix is now:
Conclusion: notifications are not the trigger. The common factor in all three episodes is a display-state transition (workstation lock/unlock, monitor sleep/wake) with the main window maximized on the 125% secondary monitor of a 200%/125% mixed-DPI setup. The DPI-virtualized-coordinate re-layout (exact arithmetic match documented earlier) remains the core defect, and
(-112,-1162)-(2661,11)is its deterministic signature.ouc3www commentedon Oct 26, 2026
Additional recovery-experiment data point (negative result):
Remote ShowWindow re-layout does NOT recover the freeze; session lock/unlock does.
During the ongoing third episode (notifications off, ~10:50–11:30 local), while the desktop was frozen, I executed a remote repair from an external process against the OpenCode main window:
All three calls succeeded at the Win32 level (no error, window re-laid out). At that moment the main window's DWM rect read as the correct physical-coordinate fullscreen rect
(-128,-1440)-(3312,0)(3440x1440 on the secondary monitor), not the frozen signature rect(-112,-1162)-(2661,11).Result: mouse input to other applications remained swallowed. The freeze only cleared after the user locked the workstation and logged back in.
Implications:
WindowFromPoint()hit-testing andGetForegroundWindow()survives a full ShowWindow cycle.OpenCode.exeWin+Ctrl+Shift+BWatcher is still running; I'll keep posting signatures as episodes occur.
ouc3www commentedon Oct 26, 2026
One more fine-grained symptom observation from the third episode, which further narrows where the input wedge lives:
During the freeze: mouse wheel scrolling still worked on the other, visible-but-unclickable windows (e.g. Chrome, Explorer content scrolled normally), while mouse clicks and text selection were completely swallowed.
This splits the input routing into two paths with different behavior:
WindowFromPoint→WM_NCHITTEST→WM_LBUTTONDOWN)Chrome_WidgetWin_1WM_MOUSEWHEEL)GetForegroundWindow()NULLduring total freeze)So the invisible window intercepts the hit-test path (clicks) but wheel messages still reach the underlying windows. That means the wedge is not a wholesale message-queue failure — it is specific to the hit-test/click path, consistent with an invisible-but-hit-testable window sitting on top, while queued/routed messages (wheel) bypass it.
One caveat I can't confirm: the wheel may have been reaching the underlying windows via per-monitor cursor routing ("scroll inactive windows") rather than focus routing, since
GetForegroundWindow()wasNULLpart of the time. Either way, the clicks-vs-wheel asymmetry seems like a strong fingerprint for whoever debugs the hit-test layer.Next episode I'll also test the keyboard path (Alt+Tab, typing) to complete the matrix. Watcher keeps running.
ouc3www commentedon Oct 26, 2026
Keyboard-path results from the latest episode, completing the input matrix:
XamlExplorerHostIslandWindow'任务切换' while clicks were still eaten)Updated input-path matrix during a freeze:
WindowFromPoint→WM_NCHITTEST)WM_MOUSEWHEEL)Everything except the hit-test path stays functional. The whole wedge is confined to
WindowFromPoint-based hit-testing — i.e. there is an invisible but hit-testable top-level window (OpenCode'sChrome_WidgetWin_1with the stale DPI-virtualized rect(-112,-1162)-(2661,11), documented earlier) that captures pointer-button routing while all queued/routed input (wheel, keys) continues to work.Not tested yet (will try next episode): right-click and middle-click (predicted dead, same hit-test path).
ouc3www commentedon Oct 26, 2026
Two new observations from the latest episodes that significantly refine the mechanism:
1. The wedge is spatially partial — consistent with an overlapping invisible window, not a global input failure.
In one episode, Windows Explorer's left navigation pane remained clickable while the right file pane was dead (wheel worked everywhere, clicks/selection dead on the covered area). This matches the frozen rect documented earlier:
(-112,-1162)-(2661,11)covers only part of the desktop — clicks die exactly where the invisibleChrome_WidgetWin_1overlaps, and work elsewhere.2. Opening a right-click context menu sometimes CLEARS the wedge.
In the episode above, right-clicking the (still clickable) nav pane popped a context menu — and after the menu was dismissed, the previously dead area became clickable again. No lock screen needed.
Recovery methods now:
OpenCode.exeWin+Ctrl+Shift+BRefined hypothesis: stuck mouse capture. A context menu runs a modal menu loop that seizes mouse capture (
SetCapture/SetForegroundWindowinternals) and then releases it properly on dismissal. If the invisible OpenCode window was holding a stuck capture (or a stale capture/hit-test state after the DPI-transition re-layout), the menu loop forcibly breaks that state. This also fits the earlier observation that a full ShowWindow re-layout does not clear the wedge (capture state is independent of placement), while events that reset capture/foreground (menu loop, session lock) do.Next episode I'll query
GetGUIThreadInfo()from an external process during the freeze to checkhwndCapture— if it points at the invisible OpenCode window, the capture hypothesis is confirmed, and postingWM_CANCELMODEto that window should be a targeted remote fix.ouc3www commentedon Oct 26, 2026
Update: the bug reproduces with the main window on the PRIMARY (200%) monitor — the 125% secondary is not a necessary condition.
Experiment performed: moved OpenCode's main window from the secondary monitor to the primary (200%, 3120x2080). Shortly after, a new episode occurred (user working in Outlook on the primary screen: clicks/selection dead in part of the window, wheel fine; a few right-clicks popping context menus restored clicking again — the context-menu recovery reproduced a second time).
Watcher capture during the episode (all rects below are DWM physical-pixel rects read from an external process):
Healthy state of the same window (captured minutes earlier):
rect=(0,0)-(3120,1984)= full primary workarea in physical pixels.So during this episode the window was laid out at 50% of its correct size — the 200% monitor's DPI-virtualized logical coordinates used as physical pixels — same defect shape as the earlier secondary-monitor episodes (there the factor was 1.25), while windows of other processes were unaffected. The "glass" only covered part of the primary screen, which again matches the spatially-partial click dead zones the user experiences.
Consequences for the reproduction recipe:
WM_DPICHANGED; the watcher also briefly saw the old stale secondary-monitor rect during the move window, suggesting the bad re-layout can trigger on the DPI change of a cross-monitor move alone, no lock/unlock required).WM_DPICHANGED/WM_DISPLAYCHANGEclass events.Context-menu recovery has now worked twice (Explorer nav pane, Outlook). Still pending:
GetGUIThreadInfocapture dump +WM_CANCELMODEtargeted fix during an active freeze (script ready, waiting for the next episode).ouc3www commentedon Oct 26, 2026
Correction to my previous comment (the "stale 50% layout on the primary" claim): that inference was a measurement artifact. Comparing rects across two processes with different DPI-awareness mixes coordinate frames — in the watcher's (virtualized) frame, a healthy maximized-on-primary window reads exactly
(-7,-7)-(1566,998)(verified against another app's window). So episode 4's rect was not demonstrably stale; what remains solid is the symptom: clicks dead in parts of the primary screen while the top window under the cursor was OpenCode's main window, wheel/keyboard fine, right-click menus recovering it.Meanwhile, a window enumeration during a healthy period surfaced two relevant facts:
Chrome_WidgetWin_1); there is no separate permanent overlay window in the process. (SeveralSoBS_*Sogou-IME windows also live in the process but are HIDDEN — the IME is ruled out, consistent with earlier findings.)LockScreenInputOcclusionFrame("Input Occlusion Window") — TOPMOST, visible, covering the entire secondary monitor — even in a healthy, unlocked state.WindowFromPointon secondary-screen points returns this window, not the app windows below it. Presumably it normally passes input through (returnsHTTRANSPARENT), but its presence right after lock/unlock episodes makes it a suspect worth ruling in or out.Current leading hypothesis: DWM cloaking. To explain "an invisible window that still hit-tests and eats clicks, while the visible windows below it receive wheel/keyboard": a window that is cloaked (DWM stops rendering it but it stays marked visible, keeps its z-order, and remains hit-testable) fits every observed symptom. Electron/Chromium cloaks windows for occlusion/power-saving tracking, and the episodes all follow DPI/display transitions — exactly where occlusion detection could mis-fire. A cloaked OpenCode window sitting maximized (last-foreground z-order) over a monitor = the invisible glass.
To test this, the watcher now records
DWMWA_CLOAKEDfor every sampled window (TOP and FG) every 2 s. The next freeze should show eitherCLOAKED≠0on the OpenCode window, or implicate the shell occlusion window, or rule both out. Will report with data.opencodejr commentedon Oct 28, 2026
Thanks for all the detailed captures and experiments. They narrow this down a lot.
I couldn't reproduce this here: it needs the Windows desktop app on two real monitors at different scales, and the automated environment I run in can't provide that. A maintainer with a similar setup will need to look at it.
What they'll try, based on your comments:
If your watcher catches another episode, the
DWMWA_CLOAKEDandGetGUIThreadInfo(hwndCapture) data you mentioned would be very useful to post here. So would a note on whether a cross-monitor drag alone, with no lock/unlock, is enough to trigger it.ouc3www commentedon Oct 28, 2026
Update: still reproduces on 2.2.0 (released after 2.1.4). Upgraded this morning;
OpenCode.exeFileVersion = 2.2.0.0 — and an episode occurred ~20 min later on the new version:The frozen rect signature is identical to the 2.1.4 episodes (same monitor geometry, same border padding) — so whatever computes this bad re-layout is unchanged in 2.2.0.
New live diagnostic from this episode (ran
GetGUIThreadInfofrom an external process while the wedge was active):Two conclusions:
DWMWA_CLOAKEDread 0 when I queried it ~1 min after the episode (timing caveat: possibly after the wedge self-resolved, so cloak is not yet ruled out — the dedicated CLOAKED watcher sampled too coarsely and missed this episode; I'm tightening it).Also relevant for the maintainer trying to reproduce: episodes continued across an app process restart with the identical rect — the frozen rect is a deterministic function of the monitor geometry (secondary 3440x1440@125% at negative Y), not of process state. And it reproduces on the primary 200% monitor too (previous comment).
Continuing to watch on 2.2.0; will post
DWMWA_CLOAKEDfrom the improved watcher on the next episode.fix(desktop): drop the display recovery workaround
Hona commentedon Oct 30, 2026
Thanks for the detailed captures. Your 2.2.0 data changes the picture: the window keeps its correct maximized rect
(-128,-1440)-(3312,0)but is not drawn, while it still takes clicks. OpenCode only creates a standard frameless Electron window, so the next step is to find which layer breaks: Electron's frameless window, Chromium in general, or Windows.Could you run these three windows on the same setup, one at a time? For each one, maximize it on the 125% monitor, run the watcher, and lock/unlock and sleep/wake the monitors as usual.
npx [email protected] .in that folder.repro-59229on the first non-primary display.FRAMELESS = falseinmain.js, or runnpx [email protected] . --framed.Watcher: https://gist.github.com/Hona/d21d6d622bc2bba77d5a873229641cdf
repro-59229and checks four points inside it every 250 ms, in physical pixels.WindowFromPointreturns the window but the screen pixel is not magenta is logged asGLASS. It also logs the rect,DWMWA_CLOAKED, DPI, monitor handle and foreground state.glass-watch.jsonlnext to the script.Results that would help most:
GLASSseen?GLASSlinePlease also include the output of:
ouc3www commentedon Oct 30, 2026
Repro harness is running on the exact setup. Status update:
Setup (all on Windows 11, primary 3120x2080 @ 200% at (0,0), secondary 3440x1440 @ 125% at negative Y):
(-128,-1440)-(3312,0)— pixel-identical to the OpenCode glass-window rects we captured during real episodes, so the repro geometry matches the bug exactly.glass-watch.ps1runs at 250 ms; OpenCode-side watchers (DWMWA_CLOAKED + rect sampling) continue in parallel.Trigger cycles attempted so far (all negative):
The only GLASS lines logged are lock-screen/screen-off cover (black/lock-screen pixels over the window for < 2 s, recovering immediately) — expected, not the bug.
DWMWA_CLOAKEDstayed 0 throughout.Also of note: since upgrading the real app to 2.2.0 the user reports episodes have become noticeably rarer (though at least one episode did occur on 2.2.0, documented above). So the trigger is strongly intermittent — short forced cycles may simply not hit it.
GPU: Intel(R) Arc(TM) Graphics, driver 31.0.101.5382 (2024-03-27) — single GPU, both monitors on it.
Plan: keep the frameless repro window maximized on the 125% monitor during normal work (with the watcher), repeat lock/unlock + sleep/wake cycles over the coming days, and run windows 2 (framed) and 3 (Chrome) for comparable exposure. I'll post the matrix as soon as any cell produces data — positive or after meaningful exposure. If you'd rather we prioritize a specific window, say the word.