We run four to ten Claude Code sessions at once, and several of them need a browser. We never launch one. The agent drives the browser itself, either through the Chrome DevTools MCP server or by attaching over CDP to a slot that is already up:
browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")From there it opens pages, fills forms, takes screenshots, and clicks through admin screens that have no clean API. When it works, we never think about it.
The trouble was how often it didn’t work, and where the failure landed. The same setup would run clean one session and come apart the next, and every failure fell on us in the middle of a run we weren’t watching.
the browser was the least reliable thing in the loop
The failures came in a few shapes, and we saw each of them more than once.
A browser would open somewhere we couldn’t see it. The agent launched a window onto a monitor region that wasn’t there, or off the edge of the screen. The agent was doing something, and we couldn’t watch it do it.
Sessions stepped on each other. With several agents running, two would reach for the same browser state and one would overwrite the other’s tabs and logins. One agent’s work would vanish underneath another’s, and nothing announced it.
The failure that bothered us most was an agent reaching for our own Chrome, the browser we use all day, and then stopping to ask us to take control of it. That pulls us into the middle of an unattended run, and our real browser, with our real logins, becomes part of the machinery.
when the browser fails, it fails onto us
In a normal app, an unreliable integration is friction you learn to tolerate. In an autonomous workflow it costs more. The point is that the agents run without us. A browser that needs us to find its window, untangle whose session is whose, or hand over a personal Chrome drops us back into the loop at the worst moment: mid-task, with none of the context loaded, having to reconstruct what an agent was doing and why it suddenly needs us.
The browser was the least reliable part of the system, and it was load-bearing. Half of what makes the agents useful runs through a browser at some point.
the wrong turn: one more setting
For a while we treated it as a configuration problem. We changed a flag, pointed at a different profile, tried the next option. Each change made things a little better, and then it broke in a new way. We kept going because we kept expecting one more setting to make it stable.
It never did. All that time went into a question configuration can’t answer. One agent driving one browser is a configuration question. Ten agents sharing one machine, unattended, all day, is a coordination problem: who gets which browser, where its window goes, whose identity it carries, and what happens when two of them want the same thing at the same instant.
the part we had to build
We already ran a fixed pool of numbered browser slots. We had built it months earlier for a different reason: per-session Chrome profiles had filled our system drive until the machine froze, and a fixed pool capped them. That pool controlled how many browsers existed. It never coordinated them. So we built the manager that does, one layer per failure mode:
- A cross-process lock on slot allocation. The clobbering was a classic race: two agents grabbing the same slot at the same instant. A real cross-process lock around allocation, plus atomic lease writes, ended it. This was the priority-zero fix.
- A pinned, isolated browser binary. Slots run a dedicated Chrome-for-Testing build under their own directory, never our system Chrome. That closed the “it reached for our personal browser” hole outright, and it sidesteps a second trap where modern Chrome binds saved logins to one specific binary.
- Monitor-aware tiling. On launch the manager reads the real monitors, excludes the taskbar area, strips Chrome’s remembered window position so it can’t restore off-screen, and tiles each window into a visible grid. Over-capacity windows cascade on top of the grid instead of flying off into nowhere.
- Sticky logins per repo. Slots are keyed to the repo or login target, so the agent working on a given site comes back to the same authenticated window instead of logging in from scratch every run.
Each layer killed one of the symptoms we had been living with. Together they turned the browser from the flakiest thing in the loop into something we stopped having to think about.
still tuning, on purpose
It is consistent and stable now, and getting it there took half-days of tweaking and testing rather than one clever fix. The detail we’re turning right now is window size. The grid is set to two-by-two so each agent’s browser gets a readable quarter of the screen instead of a cramped tile. That is a config value, which is exactly where we want a decision like that to live.
We don’t regret the time. We touch this every single day, and when something is load-bearing and in constant use, making it reliable early pays for itself. Every day you put it off, it taxes everything that runs on top of it.
In this setup, a lock on slot allocation, an isolated browser binary, visible window placement and sticky logins per repo each removed one failure mode. If you run several agents at once and any of them use a browser, find out what coordinates those browsers before the next unattended run finds out for you.