[GH-ISSUE #3193] [Bug] WebMCP discovery omits domain shutdown before detaching #1129

Closed
opened 2026-10-15 18:31:20 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3193

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

A successful rea discover-webmcp-tools call enables the WebMCP CDP domain but never sends WebMCP.disable before detaching the target and closing its connection. An independent verification against the repository's loopback HTTP/WebSocket CDP fixture returned operation: "discover_webmcp_tools", status: "available", and the search_orders registration. The CLI exited successfully with empty stderr. The trace contains WebMCP.enable, Page.disable, and Target.detachFromTarget, but no WebMCP.disable. The fixture reported zero active connections after completion.

Reproduction

  1. From a clean checkout of the baseline, install its locked dependencies and build the CLI. Use the repository's pinned Node.js toolchain.
npm ci
npm run build:cached
  1. Create the following temporary observation test. It starts only a loopback fake endpoint and invokes the supported CLI with its endpoint, the allowed page target, and a zero observation duration. The test asserts the baseline's omission so the observed method trace remains available even when the test passes.
cat > src/browser/repro.test.ts <<'TEST'
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { expect, it } from "vitest";
import { startFakeCdpBrowser } from "../../tests/fixtures/fakeCdpBrowser.js";

it("records WebMCP discovery cleanup through the built CLI", async () => {
  const browser = await startFakeCdpBrowser({ webMcpTools: true });
  try {
    const { stdout, stderr } = await promisify(execFile)(process.execPath, [
      "scripts/rea.mjs", "discover-webmcp-tools", browser.endpoint,
      "allowed-page", "--observation-ms", "0", "--json",
    ], { cwd: process.cwd(), env: process.env, maxBuffer: 16 * 1024 * 1024 });
    const output = JSON.parse(stdout);
    const methods = browser.commands.map(command => command.method);
    console.log(JSON.stringify({ operation: output.operation,
      status: output.normalized_result.status,
      toolNames: output.normalized_result.tools.items.map((tool: { name: string }) => tool.name),
      methods, activeConnections: browser.activeConnectionCount(), stderr }));
    expect(output.operation).toBe("discover_webmcp_tools");
    expect(output.normalized_result.status).toBe("available");
    expect(output.normalized_result.tools.items.length).toBeGreaterThan(0);
    expect(methods).toContain("WebMCP.enable");
    expect(methods).not.toContain("WebMCP.disable");
    expect(methods).toContain("Page.disable");
    expect(methods).toContain("Target.detachFromTarget");
    expect(browser.activeConnectionCount()).toBe(0);
  } finally { await browser.close(); }
}, 60000);
TEST
  1. Run only that test with isolated home and configuration directories.
mkdir -p .repro-home/config .repro-home/cache .repro-home/data
HOME="$PWD/.repro-home" \
XDG_CONFIG_HOME="$PWD/.repro-home/config" \
XDG_CACHE_HOME="$PWD/.repro-home/cache" \
XDG_DATA_HOME="$PWD/.repro-home/data" \
npm run test:local -- src/browser/repro.test.ts
  1. Inspect the logged method list. It contains WebMCP.enable and Target.detachFromTarget, but no WebMCP.disable. Delete the temporary test and isolated home after the run.
rm src/browser/repro.test.ts
rm -rf .repro-home

The CLI and focused test were executed against the prepared baseline. The dependency installation and build commands above describe clean-checkout preparation; they were not rerun during verification.

Expected behavior

docs/browser-observation.md:324, under Non-goals and threat model, states: "REA disables the domains it enabled, detaches the target session, and closes only its own WebSocket." The same guide identifies the WebMCP discovery workflow at lines 21 and 189. On this successful, uncancelled discovery request, cleanup should attempt WebMCP.disable after enabling that domain and before releasing the target session and transport.

Impact and scope

The reproduced defect is a cleanup-contract mismatch in one successful passive WebMCP discovery call. Discovery still returned the expected registration, detached the target session, and closed the owned connection. No result loss, persistent browser-state change, connection leak, or security impact was demonstrated. The fixture does not establish Chrome's internal domain-state lifetime. Real-browser behavior and cancellation/error paths were not tested.

The existing detach and socket close allow the observed discovery call to complete without a connection leak. They do not satisfy the documented per-domain shutdown promise.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node 26.10.0; macOS 27.0 arm64

Target and artifact context

The repository's loopback HTTP/WebSocket CDP fixture, configured with webMcpTools: true, a caller-selected allowed-page target, and --observation-ms 0. The fixture accepts WebMCP discovery and emits one registration. No real Chrome instance was used.

Interface

CLI

Earliest failure stage

Cleanup, cancellation, or lifecycle

Relevant output or evidence

operation: discover_webmcp_tools
status: available
toolNames: ["search_orders"]
methods: ["Target.attachToTarget","Page.enable","Page.getFrameTree","WebMCP.enable","Page.getFrameTree","Network.disable","Debugger.disable","Runtime.disable","Page.disable","Target.detachFromTarget"]
activeConnections: 0
stderr: ""

Typed outcome or recovery expected

Preserve the successful available discovery result and returned registration. The restoration signal is a matching WebMCP.disable attempt after WebMCP.enable and before Target.detachFromTarget and transport closure. Keep the Page shutdown attempt, target detach, and zero active connections as controls. A failed discovery, skipped enable, or missing registration does not establish restoration.

Analysis or suspected cause

Source-derived reasoning at the reported baseline: src/cli/browserAdvancedCommands.ts:47 registers the public CLI entry point, and src/browser/CdpBrowserProvider.ts:194 reaches discovery through the authorized target-session workflow. src/browser/CdpWebMcpDiscovery.ts:105 enables WebMCP. The provider's cleanup block at src/browser/CdpBrowserProvider.ts:334 passes the fixed list defined at line 76, ["Network", "Debugger", "Runtime", "Page"], which omits WebMCP. src/browser/CdpTargetSession.ts:128 disables only that supplied list before detaching at line 145 and closing at line 159. This source trace explains the observed missing command.

Additional context

Related reports #557, #2597, and #2626 concern cancellation/frame pruning, retained Playwright cleanup after cancellation, and static optional WebMCP declarations, respectively; this report concerns an omitted CDP domain shutdown attempt during successful, uncancelled passive discovery.

The verifier assessed P3 because the missing command violates the documented contract while the same call returns the expected registration, detaches, and closes its owned connection, with no consequential behavior change demonstrated.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3193 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior A successful `rea discover-webmcp-tools` call enables the WebMCP CDP domain but never sends `WebMCP.disable` before detaching the target and closing its connection. An independent verification against the repository's loopback HTTP/WebSocket CDP fixture returned `operation: "discover_webmcp_tools"`, `status: "available"`, and the `search_orders` registration. The CLI exited successfully with empty stderr. The trace contains `WebMCP.enable`, `Page.disable`, and `Target.detachFromTarget`, but no `WebMCP.disable`. The fixture reported zero active connections after completion. ### Reproduction 1. From a clean checkout of the baseline, install its locked dependencies and build the CLI. Use the repository's pinned Node.js toolchain. ```sh npm ci npm run build:cached ``` 2. Create the following temporary observation test. It starts only a loopback fake endpoint and invokes the supported CLI with its endpoint, the allowed page target, and a zero observation duration. The test asserts the baseline's omission so the observed method trace remains available even when the test passes. ```sh cat > src/browser/repro.test.ts <<'TEST' import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { expect, it } from "vitest"; import { startFakeCdpBrowser } from "../../tests/fixtures/fakeCdpBrowser.js"; it("records WebMCP discovery cleanup through the built CLI", async () => { const browser = await startFakeCdpBrowser({ webMcpTools: true }); try { const { stdout, stderr } = await promisify(execFile)(process.execPath, [ "scripts/rea.mjs", "discover-webmcp-tools", browser.endpoint, "allowed-page", "--observation-ms", "0", "--json", ], { cwd: process.cwd(), env: process.env, maxBuffer: 16 * 1024 * 1024 }); const output = JSON.parse(stdout); const methods = browser.commands.map(command => command.method); console.log(JSON.stringify({ operation: output.operation, status: output.normalized_result.status, toolNames: output.normalized_result.tools.items.map((tool: { name: string }) => tool.name), methods, activeConnections: browser.activeConnectionCount(), stderr })); expect(output.operation).toBe("discover_webmcp_tools"); expect(output.normalized_result.status).toBe("available"); expect(output.normalized_result.tools.items.length).toBeGreaterThan(0); expect(methods).toContain("WebMCP.enable"); expect(methods).not.toContain("WebMCP.disable"); expect(methods).toContain("Page.disable"); expect(methods).toContain("Target.detachFromTarget"); expect(browser.activeConnectionCount()).toBe(0); } finally { await browser.close(); } }, 60000); TEST ``` 3. Run only that test with isolated home and configuration directories. ```sh mkdir -p .repro-home/config .repro-home/cache .repro-home/data HOME="$PWD/.repro-home" \ XDG_CONFIG_HOME="$PWD/.repro-home/config" \ XDG_CACHE_HOME="$PWD/.repro-home/cache" \ XDG_DATA_HOME="$PWD/.repro-home/data" \ npm run test:local -- src/browser/repro.test.ts ``` 4. Inspect the logged method list. It contains `WebMCP.enable` and `Target.detachFromTarget`, but no `WebMCP.disable`. Delete the temporary test and isolated home after the run. ```sh rm src/browser/repro.test.ts rm -rf .repro-home ``` The CLI and focused test were executed against the prepared baseline. The dependency installation and build commands above describe clean-checkout preparation; they were not rerun during verification. ### Expected behavior `docs/browser-observation.md:324`, under Non-goals and threat model, states: "REA disables the domains it enabled, detaches the target session, and closes only its own WebSocket." The same guide identifies the WebMCP discovery workflow at lines 21 and 189. On this successful, uncancelled discovery request, cleanup should attempt `WebMCP.disable` after enabling that domain and before releasing the target session and transport. ### Impact and scope The reproduced defect is a cleanup-contract mismatch in one successful passive WebMCP discovery call. Discovery still returned the expected registration, detached the target session, and closed the owned connection. No result loss, persistent browser-state change, connection leak, or security impact was demonstrated. The fixture does not establish Chrome's internal domain-state lifetime. Real-browser behavior and cancellation/error paths were not tested. The existing detach and socket close allow the observed discovery call to complete without a connection leak. They do not satisfy the documented per-domain shutdown promise. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node 26.10.0; macOS 27.0 arm64 ### Target and artifact context The repository's loopback HTTP/WebSocket CDP fixture, configured with `webMcpTools: true`, a caller-selected `allowed-page` target, and `--observation-ms 0`. The fixture accepts WebMCP discovery and emits one registration. No real Chrome instance was used. ### Interface CLI ### Earliest failure stage Cleanup, cancellation, or lifecycle ### Relevant output or evidence ```text operation: discover_webmcp_tools status: available toolNames: ["search_orders"] methods: ["Target.attachToTarget","Page.enable","Page.getFrameTree","WebMCP.enable","Page.getFrameTree","Network.disable","Debugger.disable","Runtime.disable","Page.disable","Target.detachFromTarget"] activeConnections: 0 stderr: "" ``` ### Typed outcome or recovery expected Preserve the successful `available` discovery result and returned registration. The restoration signal is a matching `WebMCP.disable` attempt after `WebMCP.enable` and before `Target.detachFromTarget` and transport closure. Keep the Page shutdown attempt, target detach, and zero active connections as controls. A failed discovery, skipped enable, or missing registration does not establish restoration. ### Analysis or suspected cause Source-derived reasoning at the reported baseline: `src/cli/browserAdvancedCommands.ts:47` registers the public CLI entry point, and `src/browser/CdpBrowserProvider.ts:194` reaches discovery through the authorized target-session workflow. `src/browser/CdpWebMcpDiscovery.ts:105` enables WebMCP. The provider's cleanup block at `src/browser/CdpBrowserProvider.ts:334` passes the fixed list defined at line 76, `["Network", "Debugger", "Runtime", "Page"]`, which omits WebMCP. `src/browser/CdpTargetSession.ts:128` disables only that supplied list before detaching at line 145 and closing at line 159. This source trace explains the observed missing command. ### Additional context Related reports #557, #2597, and #2626 concern cancellation/frame pruning, retained Playwright cleanup after cancellation, and static optional WebMCP declarations, respectively; this report concerns an omitted CDP domain shutdown attempt during successful, uncancelled passive discovery. The verifier assessed P3 because the missing command violates the documented contract while the same call returns the expected registration, detaches, and closes its owned connection, with no consequential behavior change demonstrated.
bsakaguchi 2026-10-15 18:31:20 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Delivered on main in fffb07419.

Shutdown did not disable the enabled WebMCP domain. The target-session cleanup owner now disables it before detach.

Validation: CDP cleanup order regression and actual Chrome CLI/MCP verifier. The verifier does not inspect Chrome internal state beyond the exposed protocol. Full exact-revision qualification passed.

<!-- gh-comment-id:6177636510 --> @morluto commented on GitHub (Oct 14, 2026): Delivered on main in [fffb07419](https://github.com/morluto/rea/commit/fffb0741936067530d1ce9777461cd53e87398c2). Shutdown did not disable the enabled WebMCP domain. The target-session cleanup owner now disables it before detach. Validation: CDP cleanup order regression and actual Chrome CLI/MCP verifier. The verifier does not inspect Chrome internal state beyond the exposed protocol. Full exact-revision [qualification](https://github.com/morluto/rea/actions/runs/38629313022) passed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1129
No description provided.