Skip to content

fix(mcp): close the browser once when the client disconnects - #43139

Closed
Pavel Feldman (pavelfeldman) wants to merge 1 commit into
microsoft:mainfrom
pavelfeldman:fix-43098
Closed

Pavel Feldman (pavelfeldman) wants to merge 1 commit into
microsoft:mainfrom
pavelfeldman:fix-43098

Conversation

@pavelfeldman

@pavelfeldman Pavel Feldman (pavelfeldman) commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Summary

  • On stdin end, both the MCP transport close and the exit watchdog closed the browser, and the second close force-killed it. Remove the 'end' handler so the watchdog is the only shutdown path.
  • Processes that exit when their parent dies listened for stdin 'close' without reading stdin, so the event never fired. Add onParentProcessExit(), which reads stdin and skips terminals, and use it in the MCP watchdog, run-server, the test server, the trace viewer, and the dashboard app.
  • Set up the MCP watchdog after the stdio transport has attached, so that it does not consume the client's first messages.

Scenarios where self-destruction did not work

Node emits 'close' on process.stdin only after stdin has been read to the end. These processes attached a 'close' listener, but nothing read stdin, so they never noticed that the parent had exited:

Process stdin comes from What happened
run-mcp-server --port, run-test-mcp-server --port the process that started the server kept running after the parent exited
run-server the process that started the server kept running after the parent exited
test-server (VS Code extension), --ui the process that started the server kept running after the parent exited
annotate client (dashboardApp.js --annotate) the MCP server or CLI daemon that runs browser_annotate stayed alive after the MCP server exited, so the dashboard stayed in annotate mode. Hidden until now, because the removed 'end' handler killed it through the abort signal
dashboard daemon started by playwright-cli show, before it reports ready the CLI, which destroys the pipe after 60 s to stop a daemon that never became ready the destroy had no effect

Already working: run-mcp-server in stdio mode (the SDK transport reads stdin) and the trace viewer in --stdin mode (it reads trace URLs). Not affected: the CLI daemon and the dashboard daemon started by browser_annotate get /dev/null as stdin. It never emits 'close', and both are meant to outlive their parent.

Fixes #43098

On stdin end, the MCP server closed its transport, and the exit watchdog
closed the browsers at the same time. Whichever path came second
force-killed the browser. Remove the 'end' handler, so the watchdog is
the only shutdown path.

Processes that must exit when their parent dies listen for 'close' on
stdin. Node only emits it once stdin has been read to the end, so most
of these listeners never fired. Add onParentProcessExit(), which reads
stdin and skips terminals, and use it in all such processes.

Fixes: microsoft#43098
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

7 flaky ⚠️ [chromium-library] › library/chromium/chromium.spec.ts:179 › serviceWorker(), and fromServiceWorker() work `@chromium-ubuntu-22.04-arm-node20`
⚠️ [chromium-library] › library/video.spec.ts:762 › screencast › should work with video+trace `@chromium-ubuntu-22.04-arm-node20`
⚠️ [chromium-library] › library/video.spec.ts:356 › screencast › should work for popups `@realtime-time-library-chromium-linux`
⚠️ [chromium-library] › library/webmcp.spec.ts:238 › should call same-name tools in their own frame `@chromium-ubuntu-22.04-node24`
⚠️ [chromium-library] › library/screencast.spec.ts:28 › screencast.start delivers frames via onFrame callback `@chromium-ubuntu-22.04-node20`
⚠️ [firefox-library] › library/browsercontext-cookies-third-party.spec.ts:257 › third party 'Partitioned;' cookies `@firefox-ubuntu-22.04-node20`
⚠️ [firefox-library] › library/browsercontext-cookies-third-party.spec.ts:470 › top level 'Partitioned;' cookie and same origin iframe `@firefox-ubuntu-22.04-node20`

52653 passed, 1270 skipped


Merge workflow run.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Test results for "MCP"

2 failed
❌ [firefox] › mcp/cli-core.spec.ts:43 › click button @mcp-windows-latest-firefox
❌ [firefox] › mcp/idle-timeout.spec.ts:19 › closes the browser after the idle timeout and relaunches it on the next call @mcp-windows-latest-firefox

9059 passed, 1495 skipped


Merge workflow run.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Hi, I'm the Playwright bot and I took a first look at the CI failures.

🟢 Both failures are pre-existing flakes — this PR is clear

The two Windows/Firefox failures have the same test identities and error signatures on unrelated SHAs. This PR's shutdown changes activate when stdin closes; these failures happened during active client calls, and the new disconnect/shutdown tests passed.

Details

Overall: the MCP report has 2 failures, both confirmed pre-existing flakes. The separate tests 1 report has only 7 retry-rescued flakes and no real failures.

Pre-existing flake / infra

  • [firefox] › mcp/cli-core.spec.ts:43 › click button (@mcp-windows-latest-firefox) — failed here because the click snapshot was undefined. The same exact test and expect(received).toBeTruthy() / Received: undefined signature has failed on unrelated main and PR runs, including 35754542981 and 37028988552. Recent aggregated results recorded 6 failures in 442 executions, with 436 passes.
  • [firefox] › mcp/idle-timeout.spec.ts:19 › closes the browser after the idle timeout and relaunches it on the next call (@mcp-windows-latest-firefox) — failed here with Target page, context or browser has been closed during relaunch. The same exact test and signature failed on unrelated main runs 37311287956 and 37321971666, and on several unrelated PRs. A recent sample recorded 4 failures in 24 Windows/Firefox runs, with 20 passes.

Triaged by the Playwright bot - agent run

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.

[MCP]: On stdin EOF the stdio server force-kills its own browser 1 ms after starting the graceful close (Windows)

1 participant