Repository navigation
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restricted stops script files, not Add-Type; the Bypass flag only overrides a per-machine preference, while Group Policy and ConstrainedLanguage still win and the helper then dies quietly by design. Co-Authored-By: pi <pi@local>
SW_RESTORE on a maximized window shrinks it to a normal window, which is a gratuitous layout change the user never asked for. Gate the restore behind IsIconic so the helper touches minimized windows and nothing else. The raised state it produces is identical either way. Co-Authored-By: pi <pi@local>
AllSigned really does refuse -EncodedCommand, while a default Restricted box runs the helper either way, so say exactly that. -EncodedCommand lands in Script Block Logging in the clear (verified in event 4104), which is what an auditor wants to see. Co-Authored-By: pi <pi@local>
ebb5c2a to
8ffd962
Compare
|
Thanks for the detailed write-up and the careful measurements. We're going to close this one for now. We tested the current version on Windows. Clicking "Open in Explorer" brings the folder window up above all other windows. We couldn't reproduce the window landing behind other apps, so we don't have a problem here that this change would fix. Even if it does reproduce in some setups, we'd rather not ship this approach: Security tools may flag it. A hidden PowerShell process with -EncodedCommand, -ExecutionPolicy Bypass and Add-Type user32 P/Invoke is the kind of pattern antivirus and EDR tools flag. pi-web runs on a lot of work machines. Two smaller notes, in case you want to keep the branch: Unrelated AGENTS.md changes. The first commit adds about 138 lines to AGENTS.md: duplicated file-map entries and topic notes that now live in docs/agents/. If you can still reproduce the original problem, please open an issue. Include your Windows version, the browser you use, how pi-web was started (terminal, service, startup task), and any focus or foreground settings you've changed. We're happy to look at a lighter fix once we can reproduce it. |
Raise the Explorer window when "Open in Explorer" is used (Windows)
Problem
POST /api/open-in-explorerspawnsexplorer.exe <cwd>. The folder does open, butExplorer does not bring the window in front of the other applications the user
already has open, so the window appears behind all of them and the button looks
like it did nothing. Reproduced here by clicking the sidebar button with a
terminal and two chat windows open: the folder window came up fully covered.
Fix
lib/open-in-file-manager.tskeeps spawning the same file manager command, thenon Windows only follows it with a short-lived PowerShell helper
(
fileManagerFocusCommand) thatShell.Application) until a window shows thefolder — Explorer creates it asynchronously, after the launch has returned,
SetWindowPos(HWND_TOPMOST … HWND_NOTOPMOST), andWhy z-order instead of the foreground
Windows only grants the foreground to a process the user last interacted with,
and the process that receives the click is the browser, not the server, so
SetForegroundWindowfails from here (verified: it returnsfalse). The usualworkaround — synthesizing a bare Alt press to unlock it — makes the window that
was in front re-activate itself, so the folder loses the race to a chat app
(measured 4/5 on this machine).
SetWindowPoscarries no such restriction.A plain
HWND_TOPraise is not enough on its own: measured with two throwawayExplorer windows, one opened over the other,
HWND_TOPleaves the coveredwindow below (index 4 under index 3), because Windows draws the active window
above the rest of its band. The momentary topmost pass is what carries it past
that: with a 30 ms gap the window ends up above its cover and the topmost flag
dropped (verified indices and
WS_EX_TOPMOSTwithEnumWindows/GetWindowLong).The gap is slack, not load-bearing — every gap from 0 ms to 200 ms lands in the
same place — and it stays short on purpose: the one way this can misbehave is
the helper being killed between the two calls, which leaves the folder pinned
above everything until it is closed or
Always on topis unticked in itscontext menu. A test asserts the gap stays at or under 100 ms. It runs after the
response, so the request does not wait for it, and it is best effort throughout:
the folder is already open by the time it polls, so a missing PowerShell, a
blocked
Add-Type, or a window that never appears must not fail the requestthat opened the folder.
Other details
detached: true:DETACHED_PROCESSleavesPowerShell without a console and it exits without running the script.
unref()is what keeps the request from waiting for it.
normalizeExplorerLocationUrl(), becauseExplorer's
LocationURLandpathToFileURL()disagree on escaping and on thetrailing slash. The path is embedded in the script as a single-quoted
PowerShell string, so any character a path can contain survives.
IsIconicgates theSW_RESTORE: an unconditional restore would shrink amaximized window back to a normal one, so the helper returns minimized
windows and leaves every other state alone.
old behaviour.
-ExecutionPolicy Bypassonly overrides a user-level preference such as abox left on AllSigned (which really does refuse
-EncodedCommand); a GroupPolicy lockdown or ConstrainedLanguage still wins, and then the helper dies
quietly with the window merely opened.
-EncodedCommandlands in ScriptBlock Logging in the clear (verified in event 4104), so in an audited shop
this reads as machine-generated automation rather than anything hiding.
PI_WEB_FILE_MANAGER_FOCUS=0turns the raise off.Testing
lib/open-in-file-manager.test.mjs: 6 tests (URL folding, the focus commandper platform, the raised script including the maximized-window gate and the
short topmost gap, PowerShell string escaping, the env switch).
npm test,tsc --noEmit, andnpm run lintare unchanged otherwise (the 12 failures innpm testare pre-existing on Windows: PTY, worktree, symlink and PATHcases).
screenshot: from behind 37 other windows the folder ends up above every normal
window, a minimized window is restored and raised, the window is not left
pinned, and five simultaneous clicks cost 0.83 s in total. An A/B check with
two throwaway Explorer windows shows
HWND_TOPalone cannot pass the activewindow while the 30 ms topmost pass can.