Why we drive Chrome over CDP instead of spawning a fresh browser
This is an engineering post. If you want the product pitch, it's on the home page. Here I want to explain, with receipts, why Forgeline attaches to your running Chrome over the DevTools Protocol instead of launching a clean Chromium like every other automation stack.
The numbers we were staring at
On our internal benchmark — a synthetic "open a tab, read its DOM, write a summary" task against 30 real websites — the clean-browser path cost roughly:
- ~1.8s to spawn a headless Chromium and reach
about:blank. - ~4–11s to navigate and wait for
networkidleon sites gated by auth walls, Cloudflare challenges, or consent banners. - ~0 successful completions for any workflow that required a logged-in session (12 of 30 sites).
CDP-attach against an already-open Chrome: ~40ms to establish the WebSocket, zero re-auth, and all 30 sites completed.
How the attach actually works
Chrome, started with --remote-debugging-port=9222, exposes a plain HTTP endpoint at /json/version with a webSocketDebuggerUrl. We hit that, upgrade to WebSocket, and then speak standard CDP:
GET http://127.0.0.1:9222/json/version
→ { "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/<id>" }
// over that socket, a CDP call looks like this:
→ { "id": 1, "method": "Target.getTargets" }
← { "id": 1, "result": { "targetInfos": [ ... tabs you already have open ... ] } }
From there, Target.attachToTarget gives us a session on the tab the user picked, and we drive it through Page, DOM, and Runtime domains. Playwright's connect_over_cdp is what we use under the hood — we just strip out the "launch a new context" default.
The Chrome 136 wall
Starting Chrome 136, --remote-debugging-port is silently ignored if you point it at the default user-data-dir. This is deliberate — a mitigation against cookie-theft malware that was abusing CDP to exfiltrate logged-in sessions. Absolutely the right call from the Chrome team.
Our workaround is a dedicated profile directory (%USERPROFILE%/ChromeAuto on Windows, $HOME/ChromeAuto elsewhere). The installer creates it, launches Chrome once with --user-data-dir pointing at it, and from then on the user signs into whatever services they want the agent to reach. Your real, daily-driver profile is never touched.
What we gave up
Parallelism, honestly. A CDP-attached browser is shared state — two concurrent tasks yanking the active tab in different directions is a race condition waiting to eat someone's afternoon. We lock at the "page" level: one task per tab, and Target.createTarget when we want isolation. It's not as clean as "spin up a fresh context per task," but for one-human-at-a-time workflows it has been fine.
When a fresh browser is the right answer
Scraping at scale. Regression tests. Any workflow where stateless execution is a feature, not a bug. Forgeline isn't trying to be the right tool for those jobs. Playwright and Puppeteer already are.
But when the job is "do the thing I was about to do myself" — Forgeline borrowing your session is the difference between an agent that works and an agent that gets stuck on the login screen.