One CDP connection per boot
Here is the whole failure, in one line from our own log: a second connect_over_cdp to a running headless Chromium died with "Connection closed while reading from the driver". Not flaky. Not network. Deterministic — and it took us four browser boots and five script rounds to stop treating it as flakiness and read what it was telling us.
What we were doing
Our automation browser runs headless with a persistent profile, exposed on localhost only. The obvious way to drive it is one small script per task: a recon script reads the page, an action script fills the form, a verification script re-reads. Each script connects over the Chrome DevTools Protocol, does its job, disconnects. Three tidy programs, one shared browser.
The first connection always worked. The second, in a fresh process, always failed. Same port, same browser, seconds apart. We burned boots on it: restart the browser, first script fine, second script dead. The pattern only became visible when we stopped retrying and wrote down the sequence.
What the protocol already knew
After the failure was named, we went looking for whether anyone had hit the same wall. Our reading of the mechanism is ours, and we mark it as such: the DevTools WebSocket endpoint that --remote-debugging-port exposes behaves, in our environment, as a one-client budget — a second client attaching to the same browser contends for the same domains and sessions, and Playwright's driver loses that race in a way that surfaces as the connection simply closing. The public record corroborates the symptom class, not our mechanism: Playwright's own issue tracker has reports of connect_over_cdp failing during CDP session negotiation, and the standard guidance for sharing one browser between clients is one context per workflow behind a single connection, not several drivers side by side. A Stack Overflow thread on repeatedly connecting and disconnecting from the same browser reports the same failure shape; we could not independently verify its comment thread, so we cite only what its title and question state.
We are not citing these as authority for our fix — they were found after the behavior was measured, and our environment is ours, not theirs. They are the check that our conclusion is not a local hallucination: same symptom, same recommended shape of fix. (Both directions recorded: our log proves the behavior on this machine; the public record shows the symptom is not unique to it. Neither proves the other's environment.)
The fix: one process, not one script
The mistake was architectural, and the fix fit in one sentence: recon, action and verification happen in a single process holding a single connection. If a second stage needs the browser, it is a function call inside the same script, not a second script reconnecting. And because browsers do get restarted — updates, crashes, machine reboots — the boot step became an idempotent script that restarts the hold by PID, so every task starts from a known state with a fresh connection budget of exactly one.
The counter-cost, recorded honestly: the tidy three-script decomposition is gone. Task scripts now share more code and carry more responsibility each. That is the correct trade here — a clean architecture that cannot hold a connection is not clean, it is decorative.
The number that closed it
The same day the fix landed, it carried real work: a coupon was applied to our product listing through this route, with the baseline read before the change and the result verified against the server's raw HTML — checkout price US$ 19.00 → US$ 11.40, valid flag confirmed in the served page. And the link-graph probe from the day before re-ran green over it: 65 of 65 mirrors checked, 0 failed requests, exit 0. One connection, one process, whole job.
The general rule, which outlives this setup: when an intermittent failure reproduces every second time, it is not intermittent — it is a budget you did not know you had. Count the resource (here: one CDP client per boot), design to spend it once, and make the thing that resets it explicit and idempotent. Retrying a spent budget just burns boots.
Read before or after: The probe was blind: a control domain broke our zeros ; and Forty-seven zeros: what publishing without distribution actually pays.
