From d6dcfed844df1ac6493652e03ac0d0f5842e3c27 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sat, 15 Aug 2026 06:49:30 +0000 Subject: [PATCH] fix(browser): suppressed blank shared startup window Added Chromium no-startup-window to the broker-owned browser launch so no unowned foreground page survives session tab cleanup. Covered the resolved Chromium argv and documented the Windows regression. Fixes #8615 --- packages/coding-agent/CHANGELOG.md | 4 ++++ .../coding-agent/src/tools/browser/launch.ts | 6 +++--- .../test/tools/browser-launch.test.ts | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 5597d222b..a6e8390ed 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed broker-owned headless Chromium opening and retaining an unowned blank foreground window on Windows ([#8615](https://github.com/can1357/oh-my-pi/issues/8615)). + ## [17.3.4] - 2026-08-14 ### Changed diff --git a/packages/coding-agent/src/tools/browser/launch.ts b/packages/coding-agent/src/tools/browser/launch.ts index 4c10635e1..bcf188df6 100644 --- a/packages/coding-agent/src/tools/browser/launch.ts +++ b/packages/coding-agent/src/tools/browser/launch.ts @@ -430,8 +430,8 @@ export interface SharedBrowserLaunchSpec { * Resolve the executable and complete argv for a shared Chromium the daemon * broker spawns directly (no puppeteer inside the broker). Mirrors * `launchHeadlessBrowser` flag assembly — puppeteer's default args minus the - * stealth-suppressed set — plus `--remote-debugging-port=0` so every client - * attaches over CDP. Returns null when no Chromium executable resolves; + * stealth-suppressed set — suppresses Puppeteer's unowned startup window, and + * exposes CDP on an ephemeral port. Returns null when no executable resolves; * callers fall back to a process-local launch. */ export async function resolveSharedBrowserLaunchSpec(opts: { @@ -451,7 +451,7 @@ export async function resolveSharedBrowserLaunchSpec(opts: { }); return { executablePath, - args: [...defaults.filter(arg => !ignored.has(arg)), "--remote-debugging-port=0"], + args: [...defaults.filter(arg => !ignored.has(arg)), "--no-startup-window", "--remote-debugging-port=0"], }; } diff --git a/packages/coding-agent/test/tools/browser-launch.test.ts b/packages/coding-agent/test/tools/browser-launch.test.ts index c168a7517..e8f2d33d1 100644 --- a/packages/coding-agent/test/tools/browser-launch.test.ts +++ b/packages/coding-agent/test/tools/browser-launch.test.ts @@ -4,6 +4,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { chromiumExecutableProbeForTest, + resolveSharedBrowserLaunchSpec, stealthIgnoreDefaultArgsForTest, systemChromiumCandidatesForTest, } from "@oh-my-pi/pi-coding-agent/tools/browser/launch"; @@ -44,6 +45,24 @@ describe("browser launch stealth defaults", () => { }); }); +describe("shared browser launch", () => { + it("suppresses the broker-owned blank startup window", async () => { + const previousExecutable = process.env.PUPPETEER_EXECUTABLE_PATH; + process.env.PUPPETEER_EXECUTABLE_PATH = "/test/chrome"; + try { + const launch = await resolveSharedBrowserLaunchSpec({ + headless: true, + userDataDir: "/test/profile", + }); + + expect(launch?.args).toContain("--no-startup-window"); + } finally { + if (previousExecutable === undefined) delete process.env.PUPPETEER_EXECUTABLE_PATH; + else process.env.PUPPETEER_EXECUTABLE_PATH = previousExecutable; + } + }); +}); + const UNGOOGLED_CHROMIUM_FLATPAK_ID = "io.github.ungoogled_software.ungoogled_chromium"; describe("system Chromium candidates", () => {