Skip to content

Commit bd4ec6f

Browse files
committed
Re-key playwright-core Bun ws patch to 1.61.1
The 1.61.0->1.61.1 bump leaves patchedDependencies keyed to playwright-core@1.61.0, so the bundled-ws->built-in-ws patch silently stops applying. playwright-core 1.61.1 still ships the unpatched `var ws = wrapper_default;`, which deadlocks the CDP websocket handshake under Bun. Re-key the patch (content unchanged, still applies cleanly to the 1.61.1 bundle), regenerate the lockfile, and update the patch-filename citations in the browser tooling and the browser-automation-engine ADR.
1 parent 5079875 commit bd4ec6f

9 files changed

Lines changed: 42 additions & 7 deletions

File tree

bun.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cdp-live-test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Live check: does playwright-core 1.61.1 (patched) complete a CDP websocket
2+
// handshake under Bun? Without the bundled-ws->built-in-ws patch this deadlocks.
3+
import { chromium } from "playwright-core";
4+
5+
const port = 9333;
6+
const server = await chromium.launchServer({
7+
headless: true,
8+
args: [`--remote-debugging-port=${port}`],
9+
});
10+
const wsEndpoint = server.wsEndpoint();
11+
console.log("launched, wsEndpoint:", wsEndpoint);
12+
13+
// Derive the browser-level CDP http endpoint and fetch the ws url, then attach.
14+
const res = await fetch(`http://127.0.0.1:${port}/json/version`);
15+
const info = await res.json();
16+
const cdpWs = info.webSocketDebuggerUrl as string;
17+
console.log("cdp ws url:", cdpWs);
18+
19+
const t0 = Bun.nanoseconds();
20+
const browser = await chromium.connectOverCDP(cdpWs);
21+
const elapsedMs = (Bun.nanoseconds() - t0) / 1e6;
22+
console.log("connectOverCDP RESOLVED in", elapsedMs.toFixed(1), "ms");
23+
24+
const ctx = browser.contexts()[0] ?? (await browser.newContext());
25+
const page = ctx.pages()[0] ?? (await ctx.newPage());
26+
await page.goto("data:text/html,<title>patchcheck</title><h1>ok</h1>");
27+
const title = await page.title();
28+
console.log("page title:", title);
29+
30+
await browser.close();
31+
await server.close();
32+
console.log("RESULT: PASS — CDP handshake completed under Bun, no deadlock");

docs/adr/browser-automation-engine.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Findings from source inspection (v0.27.1) and external research:
2222

2323
1. **The subprocess boundary is incompatible with our trust model.** Per [Browser Fill Secret](browser-fill-secret.md) and [Browser Stealth Identity](browser-stealth-identity.md), every string that leaves the browser layer passes through per-task + cross-task secret redaction, navigation passes through SSRF/DNS-rebinding pre-flight and post-redirect re-validation, screenshots go only to a vision side-call with pre-blur and post-OCR redaction, and uploads are approval-gated. All of that lives on in-process hooks into the Playwright session. agent-browser offers no interception points — adopting it means rebuilding the trust machinery as wrappers around opaque CLI text output, or weakening it.
2424
2. **Its core innovation is one we already have.** The `@eN` ref snapshot model — the reason agent-browser wins benchmarks against Playwright MCP — is structurally what `src/tools/browser.ts` already produces, with budgeting agent-browser lacks (32 KB default cap, hidden-element budget, middle-out truncation).
25-
3. **It is weakest exactly where we need the most reliability.** Headed connect for user sign-in and CDP attach to the user's own Chrome had open bugs at evaluation; the CDP-attach issue under Bun (playwright-core's bundled `ws` deadlocking the websocket handshake) is fixed in-place by `patches/playwright-core@1.61.0.patch` — a far smaller intervention than swapping the engine for one with the same class of bug and no API contract.
25+
3. **It is weakest exactly where we need the most reliability.** Headed connect for user sign-in and CDP attach to the user's own Chrome had open bugs at evaluation; the CDP-attach issue under Bun (playwright-core's bundled `ws` deadlocking the websocket handshake) is fixed in-place by `patches/playwright-core@1.61.1.patch` — a far smaller intervention than swapping the engine for one with the same class of bug and no API contract.
2626
4. **Dependency risk.** Pre-1.0, single-maintainer, experimental org, one rewrite already behind it — a poor foundation for a security-sensitive layer whose host has no programmatic contract with it.
2727

2828
agent-browser stays in our *development* workflow (driving the Next.js dev server for QA, per `CLAUDE.md`) — that use is interactive, low-trust, and plays to its strengths. This ADR is only about Gini's runtime browser tools.
@@ -62,7 +62,7 @@ The runtime's DEFAULT transport is a per-instance branded Chrome it **spawns its
6262

6363
`src/tools/chrome-launch.ts` (`launchSpawnedChrome`) launches a branded Chrome via `chromium.launchPersistentContext` over Playwright's pipe transport — `--headless=new` + the shared stealth args + a clean (non-`HeadlessChrome`) UA + the per-instance `--user-data-dir` + a free-picked `--remote-debugging-port`. `launchPersistentContext` drives the spawned page over the pipe inherently; the TCP debug port is an extra local endpoint the sign-in screencast bridge attaches to over raw CDP, **not** the automation transport. On a machine with neither a branded Chrome nor Playwright's bundled Chromium on disk, the launch downloads Playwright's Chromium on demand (`src/tools/chrome-install.ts`) and retries — so "no Chrome installed" is a supported, self-provisioning case rather than a hard failure.
6464

65-
The cdp attach (`cdpSessionProvider` in `src/tools/browser.ts`) calls `chromium.connectOverCDP(record.cdpUrl)` to the user's external Chrome and reuses its first context. We never spawn or signal that process: disconnect calls `Browser.disconnect()` when present and NEVER `close()` (which on some Playwright builds would kill the user's Chrome). `connectOverCDP` works under Bun via `patches/playwright-core@1.61.0.patch`, which routes playwright-core's bundled `ws` to Bun's built-in `ws` — without it the websocket handshake deadlocks. The cdp attach is an opt-in transport (the user must point the runtime at their own Chrome) rather than the default.
65+
The cdp attach (`cdpSessionProvider` in `src/tools/browser.ts`) calls `chromium.connectOverCDP(record.cdpUrl)` to the user's external Chrome and reuses its first context. We never spawn or signal that process: disconnect calls `Browser.disconnect()` when present and NEVER `close()` (which on some Playwright builds would kill the user's Chrome). `connectOverCDP` works under Bun via `patches/playwright-core@1.61.1.patch`, which routes playwright-core's bundled `ws` to Bun's built-in `ws` — without it the websocket handshake deadlocks. The cdp attach is an opt-in transport (the user must point the runtime at their own Chrome) rather than the default.
6666

6767
The active handle (spawned or cdp) installs into the single per-instance `shared` slot — one shared browser per instance (cookies bleed across tasks within the instance, per the explicit product decision). For the spawned handle, teardown closes the persistent context and, only if that wedges, reaps the Chrome bound to the instance's own profile dir (never `killall`, never the user's own `:9222`). See [Browser Stealth Identity](browser-stealth-identity.md).
6868

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
"protobufjs"
4242
],
4343
"patchedDependencies": {
44-
"playwright-core@1.61.0": "patches/playwright-core@1.61.0.patch"
44+
"playwright-core@1.61.1": "patches/playwright-core@1.61.1.patch"
4545
}
4646
}

src/capabilities/browser-connect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// URL. We probe the endpoint, persist the URL (credentials stripped) as the
2020
// `state.browser` record, and attach over CDP — never spawning or signalling
2121
// that process. connectOverCDP works under Bun via the bundled-ws→built-in
22-
// patch (patches/playwright-core@1.61.0.patch); it's an opt-in transport for
22+
// patch (patches/playwright-core@1.61.1.patch); it's an opt-in transport for
2323
// users who run their own Chrome.
2424
//
2525
// (The old "managed" visible-window mode was removed — issue #420.)

src/execution/browser-screencast.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// purpose-built channel, not a Playwright session. The raw WebSocket uses Bun's
1212
// native WebSocket, which is why this path always worked under Bun even when
1313
// playwright's connectOverCDP did not (that hang — playwright-core's bundled
14-
// `ws` — is fixed separately by patches/playwright-core@1.61.0.patch). The
14+
// `ws` — is fixed separately by patches/playwright-core@1.61.1.patch). The
1515
// agent's automation keeps driving the SAME Chrome over its pipe transport;
1616
// this screencast is a SEPARATE read/drive channel on the same process, so the
1717
// two never conflict.

src/tools/browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ const spawnedSessionProvider: BrowserSessionProvider = {
461461
// Attach to the user's OWN external Chrome over a CDP websocket URL they
462462
// supplied via /api/browser/connect. We NEVER spawn or kill this process — the
463463
// user owns it. connectOverCDP works under Bun via the bundled-ws→built-in-ws
464-
// patch (patches/playwright-core@1.61.0.patch); without it the CDP websocket
464+
// patch (patches/playwright-core@1.61.1.patch); without it the CDP websocket
465465
// handshake deadlocks. This is an opt-in transport for users who point us at
466466
// their own Chrome.
467467
const cdpSessionProvider: BrowserSessionProvider = {

src/tools/chrome-launch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// natural transport for a browser we spawn ourselves, no TCP debug socket
99
// needed for automation. (For attaching to a user's ALREADY-running Chrome the
1010
// cdp provider uses connectOverCDP over a TCP WebSocket, which works under Bun
11-
// via patches/playwright-core@1.61.0.patch.) We still inject a free
11+
// via patches/playwright-core@1.61.1.patch.) We still inject a free
1212
// `--remote-debugging-port` into the launch args so the spawned Chrome ALSO
1313
// exposes a debug endpoint — the sign-in screencast bridge attaches to it over
1414
// raw CDP — without routing the agent's automation through that endpoint.

0 commit comments

Comments
 (0)