Skip to content

[Windows] Visible console window per render shot: 0.8.44 resolves @puppeteer/browsers 3.2.1, where detached defeats windowsHide (fixed upstream in 3.2.2) #4028

Description

@andycfchen

Summary

On Windows, every hyperframes render opens a visible console window per browser launch (78 shots = 78 windows), stealing focus and making the machine unusable for anything else during a render.

This is not one of the windowsHide gaps already tracked in #3476 / #3500 / #3851 / #3960 / #3996 — those all cover hyperframes' own spawns. This one comes from @puppeteer/browsers, and it is already fixed upstream. 0.8.44 just resolves to the one broken version.

Root cause

hyperframes@0.8.44 declares "puppeteer-core": "^25.8.0". In practice that resolved here to puppeteer-core@25.9.0 → @puppeteer/browsers@3.2.1.

@puppeteer/browsers@3.2.1 lib/launch.js spawns the browser with both flags:

opts.detached ??= true;          // line 157
...
childProcess.spawn(this.#executablePath, this.#args, {
  detached: opts.detached,       // line 173
  windowsHide: true,             // line 174
  ...
});

On Windows libuv ORs DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP (from detached) together with CREATE_NO_WINDOW (from windowsHide) into the CreateProcessW creation flags. Those are mutually exclusive: DETACHED_PROCESS wins, the child inherits no console, and chrome-headless-shell.exe — a console-subsystem binary (IMAGE_SUBSYSTEM_WINDOWS_CUI) — gets a freshly allocated, visible console. windowsHide: true is dead code in that combination.

Upstream: puppeteer/puppeteer#15410, introduced by #15339, reverted by #15415, released in @puppeteer/browsers@3.2.2.

Evidence

Controlled A/B on the same binary, same machine, only detached differs. External EnumWindows + IsWindowVisible monitor sampling at 25 ms:

MODE=detached  ({detached:true,  windowsHide:true})  browser procs=4  NEW visible windows=1  <- class=ConsoleWindowClass proc=chrome-headless-shell
MODE=hidden    ({detached:false, windowsHide:true})  browser procs=4  NEW visible windows=0

Live capture during an actual hyperframes render, before any workaround:

class=CASCADIA_HOSTING_WINDOW_CLASS  proc=WindowsTerminal
class=PseudoConsoleWindow            proc=chrome-headless-shell
class=CASCADIA_HOSTING_WINDOW_CLASS  proc=WindowsTerminal
class=PseudoConsoleWindow            proc=chrome-headless-shell

(There is a second, independent aggravating factor worth knowing about: when Windows' Default terminal application is Windows Terminal — the Windows 11 default — the console handoff ignores CREATE_NO_WINDOW entirely, so console children pop a real Windows Terminal window even where windowsHide is correctly set. Switching to Console Host removes that layer; the @puppeteer/browsers bug above survives the switch and still produces ConsoleWindowClass windows.)

Environment

  • Windows 11 Pro 26200, Node 24
  • hyperframes@0.8.44, puppeteer-core@25.9.0, @puppeteer/browsers@3.2.1
  • Reproduced via hyperframes render --quality high and hyperframes snapshot

Suggested fix

Require puppeteer-core >= 25.10.0 (the first release that pulls @puppeteer/browsers@3.2.2). ^25.8.0 silently permits the one broken version. #3933 already bumps @puppeteer/browsers to 3.2.2 — this is the reason it matters on Windows.

Workaround for anyone hitting this now

Patch the resolved copy in the npx cache, @puppeteer/browsers/lib/launch.js:

- opts.detached ??= true;
+ opts.detached ??= process.platform !== 'win32';

Verified: renders run with zero new visible windows afterwards.

Activity

  1. added a commit that references this issue on Sep 20, 2026
    e86a530
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions