Skip to content

fix(cli): Windows Chrome watchdog ends the tree with taskkill, not a PowerShell scan - #7622

Open
miguel-heygen wants to merge 8 commits into
mainfrom
dnorton/watchdog-no-powershell
Open

miguel-heygen wants to merge 8 commits into
mainfrom
dnorton/watchdog-no-powershell

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

What

On Windows, the watchdog that ends Chrome when the CLI is killed no longer starts PowerShell to scan the process table (Get-CimInstance plus Stop-Process -Force). The CLI asks Chrome for its own process ids once a second and tells the watchdog; if the CLI dies, the watchdog runs taskkill.exe /PID <id> ... /T /F /FI "IMAGENAME eq <chrome image>" from System32 on the ids it was last given.

Why

Antivirus products on Windows flag PowerShell commands that enumerate processes and force-stop them, and that flags the CLI on every run that starts a browser. The job is "end the Chrome we started and what it started", which needs no scan.

How

  • chromeProcessIds asks Chrome SystemInfo.getProcessInfo (answered by the browser process, so a hung renderer does not matter). The CLI sends the latest list, plus Chrome's main pid, to the detached watchdog on its stdin as a ready-to-run taskkill command, once a second while Chrome runs. Chrome's own exit sends none, a clean close sends clean: neither kills.
  • Why a list and not just the main pid: when the CLI is killed, Chrome's main process exits on its closed pipe at once and a hung renderer is left with a dead parent; taskkill /T follows live parents only, and there is no PowerShell-free way to read a parent pid. The real-Chrome busy-loop test (chromeOrphans.process.test.ts) is exactly this case.
  • /FI "IMAGENAME eq …" keeps a guard against a pid Windows has since given to another program with a different image. The old creation-time check is gone with the scan, so launchedAt is no longer passed (launch.ts). The remaining gap: a listed pid that exits and is reused within the second since the last list, by a program with the same image, is killed. The creation-time check had the same shape of gap; a kill-on-close job object would close it.
  • Tried and dropped: handing Chrome's pipe ends to the watchdog so Chrome outlives the CLI. It broke the CLI's own channel to Chrome (every Windows engine test timed out on Target.createTarget).
  • The list is asked for as long as the browser runs (a check, snapshot or render), unref'd, one request at a time.

Test plan

  • Unit tests (windowsChromeWatchdog.test.ts, run everywhere): the kill is taskkill from System32 on every listed pid, never PowerShell; the watchdog runs the last list it was given, drops a pid that left the list, kills nothing after a clean close, after Chrome's own exit or before any list, and reads a list split across writes. They wait on the watchdog's own armed line; three runs green on the devbox.
  • The real-Chrome Windows tests already in the repo (chromeOrphans.process.test.ts, "leaves no Chrome process behind when the CLI is killed mid busy-loop" and mid-render) are the Windows arbiter; they run in Tests on windows-latest.
  • Documentation updated: not applicable.

fails without the fix: "runs the last list it was given when the CLI ends unclean" and "does not kill a pid that left the list before the CLI ended" (windowsChromeWatchdog.test.ts) go red when the watchdog keeps the first list instead of the last (run on the devbox); the busy-loop test in chromeOrphans.process.test.ts is red on Windows at the previous head.

What I did NOT exercise

Anything on Windows from my side: the busy-loop test result comes from the PR's Windows job. A real system Chrome (image chrome.exe), where a reused pid could belong to the person's own browser inside the one-second window.

Size

A lone fix, mostly tests and the one program swapped in one file; it shares no code with the Desktop change, which ships from another repo on its own release.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 102 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (1)

  • seqab-none-center-r0-root-z100: tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 1 blocker · 0 important · 0 nits · head 9e766742

  • blocker — the real-Chrome orphan test fails on Windows at this head. Tests on windows-latest: engine-cli-2 fails chromeOrphans.process.test.ts › "leaves no Chrome process behind when the CLI is killed mid busy-loop". After the CLI is SIGKILLed, the chrome-headless-shell.exe processes are still alive 30 s later (:69, Condition not met within 30000 ms). This is the path the PR replaces, and the new unit tests only cover it with a node.exe stand-in. Windows render verification passed on every main and merge-queue run in the last 30, so this doesn't look like an existing flake. Please either find why taskkill leaves the tree up here (for example, whether /FI with /T actually ends the busy renderer and its siblings) and fix it, or show this test green on repeated Windows runs at the head.
Scope

Read the full diff: windowsChromeWatchdog.ts, launch.ts and the new test. launchedAt and HF_CHROME_EXE/HF_CHROME_SINCE have no other users. The "fails without the fix:" line is present and holds up: with the guard reverted to if (!clean), the two named reuse-guard tests write the kill marker. CI, CodeQL, regression and preview-regression pass; Windows render verification fails as above.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 0 resolved, 1 open, 1 new · 9e766742 → 991f2e81

  • new blocker: Format fails on packages/cli/src/browser/windowsChromeWatchdog.test.ts (run bun run format). Because Preflight is red, every Windows test lane was skipped, so the busy-loop test in chromeOrphans.process.test.ts, which this PR names as its arbiter, never ran. The earlier blocker stays open until that lane is green at the new head.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant