[BUG] Windows: main window permanently always-on-top (WS_EX_TOPMOST) after silent update relaunch - stealth-relaunch z-order restore anchors to taskbar

Status Closed — not planned
Maintainer reply None cached
Activity 1 comment · opened Aug 16, 2026 · closed Aug 19, 2026

> Also reported today via the claude.ai support messenger; posting here as suggested by support so engineering can track it. This is the Windows sibling of #66516, with the mechanism identified and evidenced below.

Date: 2026-08-16
App: Claude Desktop 1.30096.5 (MSIX, Claude_1.30096.5.0_x64__pzs8sxrjxfjjc)
OS: Windows 11 25H2, build 26200.9168
Severity: High annoyance / usability — main window floats above every other application permanently; no UI setting exists to undo it.

---

Summary

After the silent auto-update relaunch ("stealth-relaunch") introduced in the 1.30096.x line, the Claude Desktop main window acquires the WS_EX_TOPMOST extended window style and keeps it permanently. The root cause is the z-order restore step: the app saves the window directly above its own main window as an "anchor" before relaunching, and re-inserts its new window behind that anchor afterwards via SetWindowPos-based moveWindowBehind. When Claude was the top window of the normal band at capture time, the window directly above it is by definition a member of the topmost band — in this incident the Windows taskbar (Shell_TrayWnd). Per documented Win32 z-order semantics, inserting a window among topmost windows makes it topmost itself. Nothing ever clears the flag afterwards.

This is a Windows-side sibling of the open macOS report (anthropics/claude-code#66516, filed 2026-06-09, labeled invalid as wrong repo), but with the exact mechanism identified and evidenced below.

User-visible impact

Claude Desktop stays on top of all other applications while the user works in those other applications. Only workarounds: minimize Claude, or strip the style bit externally (see Workaround). There is no setting in the app to disable it, and a normal user has no way to diagnose or fix this.

Timeline of the incident (all timestamps local, from logs/event log on the affected machine)

| Time | Event | Source |
|---|---|---|
| 2026-08-14 07:48–07:52 | Claude 1.30096.1 installed; first [stealth-relaunch] log lines appear 07:55:19 | AppXDeployment event log; %APPDATA%\Claude\logs\main.log |
| 2026-08-15 07:33/07:53 | MSIX update to 1.30096.5 staged | AppXDeployment event log (event 327) |
| 2026-08-15 09:29:34 | Old instance saves z-order anchor: Saved z-order anchor: 0x10146 (above our 0x104f4) | main.log |
| 2026-08-15 09:30:06 | New instance starts (main claude.exe process group StartTimes 09:30:06–09:30:10); Detected via marker file, Loaded z-order anchor: 0x10146 | process list; main.log |
| 2026-08-15 09:30:07 | Restored z-order: our 0x500aa behind anchor 0x10146 — main window enters topmost band here | main.log |
| 2026-08-16 ~07:13 | Measured main window (hwnd 0x500AA, pid 29048): GetWindowLong(GWL_EXSTYLE) = 0x00280108 → WS_EX_TOPMOST (0x8) set | live measurement |
| 2026-08-16 ~07:14 | Externally cleared via SetWindowPos(HWND_NOTOPMOST) → 0x00280100; stays cleared through the rest of the session (re-checked 07:20:32, 07:25:26) | live measurement |

The window handle in the log (0x500aa = 327850) is the same handle measured with the topmost bit ~22 hours later, and the bit was never re-asserted after external clearing — consistent with a one-shot z-order insertion at relaunch, not a repeating enforcement loop.

Evidence

A. Measurement (no third-party tooling involved)

  • GetWindowLong(hwnd, GWL_EXSTYLE) on the main window returned 0x00280108 (WS_EX_TOPMOST = 0x00000008 set).
  • No window-management utilities present or running on the machine (verified: no PowerToys, DeskPins, AutoHotkey, etc.; no PowerToys AlwaysOnTop settings exist).
  • No alwaysOnTop-related key in %APPDATA%\Claude\config.json.
  • Clearing the bit externally with SetWindowPos(hwnd, HWND_NOTOPMOST, 0,0,0,0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE) fixed the behavior until the next stealth relaunch.

B. Application log (%APPDATA%\Claude\logs\main.log)

2026-08-15 09:29:34 [info] [stealth-relaunch] Saved z-order anchor: 0x10146 (above our 0x104f4)
2026-08-15 09:30:06 [info] [stealth-relaunch] Detected via marker file (windowVisible=true, windowMinimized=false, otherAppFullScreen=false)
2026-08-15 09:30:06 [info] [stealth-relaunch] Loaded z-order anchor: 0x10146
2026-08-15 09:30:07 [info] [stealth-relaunch] Restored z-order: our 0x500aa behind anchor 0x10146

C. The anchor is the taskbar — a topmost-band window

Walking the current z-order (GetTopWindow/GetWindow) and filtering for windows with WS_EX_TOPMOST shows the topmost band on this machine contains, at the position matching the anchor handle:

hwnd=0x010146 pid=8244 class=Shell_TrayWnd title='' proc=explorer

0x10146 — the exact anchor handle from the log — is Shell_TrayWnd, the Windows taskbar, which is always in the topmost band. The handle is stable across the whole period (explorer.exe did not restart).

Why the taskbar got captured as anchor: the anchor is "the window directly above ours". When the Claude window is the top window of the normal band (very common — the user was just using Claude when the update relaunch hit), the next window up in the global z-order is the bottom of the topmost band, i.e. a topmost window such as the taskbar.

D. Code path (from resources/app.asar of 1.30096.5, minified)

Restore step (invoked at 09:30:07). Note moveWindowBehind(our, anchor) in the native module:

if(dz)try{let t=vr();if(t){let r=Zg(e);t.moveWindowBehind(r,dz),n=!0,
D.info(`[stealth-relaunch] Restored z-order: our %s behind anchor %s`,Qg(r),Qg(dz))}}

The native module @ant/claude-native/claude-native-binding.node (shipped in app.asar.unpacked) contains the strings moveWindowBehind and SetWindowPos — i.e. the reposition is done with SetWindowPos(our, hWndInsertAfter = anchor, ...).

Win32 semantics (SetWindowPos documentation, Remarks): a window can become topmost "by setting its position in the Z order so that it is above any existing topmost windows" — inserting a non-topmost window among the topmost band members transfers WS_EX_TOPMOST onto it. That is exactly what placing the main window directly behind Shell_TrayWnd (with other topmost windows below it) does. No code path subsequently clears the style.

E. Secondary, latent issue in the screenshot wrapper (hardening suggestion)

The screen-capture wrapper in the same bundle (function around the display-capture pipeline, encodeFrameJpeg context) snapshots and restores always-on-top state around each capture:

async function KEn(e){
  let t=o.BrowserWindow.getAllWindows().filter(e=>!e.isDestroyed()), n=new Map;
  for(let e of t) n.set(e.id, e.isAlwaysOnTop()), e.setContentProtection(!0);
  await Dj(UEn); // 50 ms
  try { return await e() }
  finally {
    for(let e of t) e.isDestroyed() || (e.setContentProtection(!1),
      n.get(e.id) && !e.isAlwaysOnTop() && e.setAlwaysOnTop(!0, `screen-saver`))
  }
}

Two properties make this dangerous once any transient code path (e.g. the computer-use side-panel dock R2n/z2n, which legitimately sets setAlwaysOnTop(true,'screen-saver') on the main window while docked, or the stealth-relaunch defect above) leaves or toggles topmost state concurrently:

  1. It restores at the fixed 'screen-saver' level regardless of the level it sampled — escalation.
  2. If the sampled true was transient and got cleared before the finally runs, the wrapper re-asserts topmost permanently — it perpetuates exactly the state this report is about, and can turn a temporary dock state into a sticky one under concurrency.

In this specific incident the log shows no side-panel dock events, so E is not the trigger — it is a second path to the same end state and will keep victims topmost even if they briefly get cleared.

Reproduction (expected)

  1. Windows 11, Claude Desktop 1.30096.x with pending auto-update.
  2. Have the Claude main window as the top normal (non-topmost) window — e.g. simply be using Claude. Taskbar present (default).
  3. Let the silent update relaunch happen ([stealth-relaunch] path with windowVisible=true).
  4. After relaunch, inspect the main window: GetWindowLong(GWL_EXSTYLE) & WS_EX_TOPMOST → set. The window now covers all other applications until minimized or externally cleared.

Log correlation: the incident always coincides with a [stealth-relaunch] Restored z-order: our <hwnd> behind anchor <hwnd> line where the anchor is a topmost-band window (Shell_TrayWnd being the most likely capture on an idle desktop).

Suggested fixes

  1. Anchor capture: when saving the z-order anchor, walk down and skip any window with WS_EX_TOPMOST set; only anchor to non-topmost windows (or store "we were top of the normal band" and restore with HWND_TOP).
  2. Restore hardening: after moveWindowBehind, check GetWindowLong(GWL_EXSTYLE) & WS_EX_TOPMOST on the own window and clear it with SetWindowPos(HWND_NOTOPMOST) if it was acquired (the binding currently never applies HWND_NOTOPMOST in this path).
  3. Screenshot wrapper (E): restore the sampled level instead of forcing 'screen-saver', and skip restoring windows whose topmost state was changed by another subsystem between sample and restore (compare against current intent, not a stale boolean).
  4. Optionally: expose a user-visible escape hatch ("Keep window on top" toggle in the View/Window menu) so any residual state is user-fixable — this also covers the macOS report #66516.

Workaround (for affected users)

PowerShell, run as the logged-in user:

$sig = 'using System;using System.Runtime.InteropServices;public class WT{[DllImport("user32.dll")]public static extern bool SetWindowPos(IntPtr h,IntPtr a,int x,int y,int cx,int cy,uint f);}'
Add-Type -TypeDefinition $sig
$h = (Get-Process claude | Where-Object {$_.MainWindowHandle -ne 0}).MainWindowHandle
[WT]::SetWindowPos($h, [IntPtr](-2), 0, 0, 0, 0, 0x0013)  # HWND_NOTOPMOST, NOSIZE|NOMOVE|NOACTIVATE

The state returns after the next silent update relaunch that captures a topmost anchor; a small watcher (Watch-ClaudeTopmost.ps1, checks every 30 s and logs+clears) is in use on the affected machine.

Attachments / raw data available on request

  • Full main.log excerpts, AppXDeployment event-log entries for the update timeline
  • Live GetWindowLong measurements before/after clearing
  • Z-order walk output identifying anchor 0x10146 as Shell_TrayWnd

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗