Two AI Agents, One Browser Window: What Actually Breaks, and What Doesn’t

⚡ TL;DR
  • 🤼 Two Claude Code agents shared one Brave window over CDP and spent an hour quietly sabotaging each other — while I blamed the website.
  • 🔀 The real bug was tab routing: a shared “current page” pointer. One flag, –experimentalPageIdRouting, fixes it.
  • 🔦 The macOS focus-stealing bug people build proxies for was fixed months ago — I measured it, and focus did not move.
  • 🧪 A thirty-line probe script tells you whether you even have the bug before you build anything to work around it.

I run two Claude Code agents on the same Mac, both doing browser automation against the same Brave window over the Chrome DevTools Protocol on port 9222. This morning they spent an hour quietly sabotaging each other while I blamed a website.

If you are running any kind of computer-use or browser-agent setup with more than one agent pointed at one browser, this is the failure you are going to hit, and the fix is smaller than the internet will tell you.

Here is what the failure looked like, what I wrongly concluded, and what turned out to be true when I finally measured instead of guessing.

2
Agents, one browser window
LinkedIn’s compose box vanished
5
CDP commands fired — focus never moved
30
Lines to test it on your own setup

🚨The symptoms

I was posting a technical write-up to a few places. Three things went wrong, and none of them looked related.

Text arrived twice. I’d insert a paragraph into a rich text editor and get the paragraph twice, concatenated. On Reddit this was worse than cosmetic: the doubled insert swallowed my paragraph breaks, and the editor’s autolinker then fused words across the missing breaks. “forward passes.” followed by “so i wrote” became a link to passes.so. My repo URL got welded to the next word and turned into a dead link. On a launch post.

A modal opened, then vanished. LinkedIn’s compose box would open, and by the time I went to type into it, it was gone. Six times.

Then it worked first try. I asked my partner to pause the other agent. The very next attempt went through perfectly.

🙈The wrong conclusion

I had notes from months earlier saying LinkedIn’s compose UI is hard to automate and is best done by hand. So when it failed six times, I had a ready-made explanation and I took it. I even wrote it down again as confirmation.

That’s the part worth sitting with. The evidence for “LinkedIn is hard to automate” and the evidence for “something else is driving this browser” are identical from inside one agent. I picked the explanation I already believed.

The doubled text should have tipped me off much earlier. No amount of website hostility makes your own execCommand run twice.

🔀What’s actually going on

Two separate things get called “agents fighting over the browser,” and conflating them is why people build the wrong fix.

🧭 Problem one: tab routing.

Most browser tooling has a notion of “the current page.” Two agents both calling “open a page” and “act on the current page” end up pointed at the same tab. Agent A opens a modal, agent B navigates that tab, and A’s next call lands somewhere unrecognizable. This is what was actually happening to me.

🪟 Problem two: focus stealing.

Trusted input — a real mouse click, a real keystroke — only lands in the tab the operating system has focused. There’s exactly one of those. And for a long stretch, CDP commands on macOS yanked the window forward as a side effect, even read-only ones, which meant an agent working in the background would rip your window out from under you mid-sentence.

I assumed I had both problems. I had one.

🛠️Fixing routing

chrome-devtools-mcp already ships the fix, and it’s off by default:

--experimentalPageIdRouting

It exposes a page ID on page-scoped tools and routes each request by that ID instead of a shared “currently selected” pointer. Each agent addresses its own tab explicitly. Same window, different tabs, no collisions.

One gotcha worth checking before you restart everything: if you pin the tool version, confirm your pinned version actually has the flag. I was on 1.1.1 while latest was 1.6.0. It happened to be supported there, but a silently-ignored flag would have meant restarting both agents and drawing conclusions from a config that was never live.

After turning it on, I drove tab 2 while tab 1 was the “selected” one, and it went exactly where I told it to. That was the entire bug.

🔦The focus bug is real, and already fixed

Here’s where I was about to waste a day.

There is an open ecosystem conversation about focus stealing — chrome-devtools-mcp#1254 (“macOS: Chrome steals window focus on every CDP command”), #2290 asking for passive inspection, and agent-browser#1247 proposing a background mode. People have written proxies specifically to block the commands that grab the foreground.

I had already written a focus mutex — a small lock so only one agent could hold the foreground at a time — and I was ready to argue it was necessary.

Then I read the close on #1254. The maintainer closed it on 2026-05-07: “This issue should be fixed in the latest release. The window focus should remain unchanged.” My pinned version shipped on 2026-05-27, three weeks after.

So I measured it. Chrome 150+ added an embedderData object to Target.getTargets() for tab-type targets, carrying tabActive and tabStripIndex. That lets you read which tab is in front without activating anything — which is the whole trick, because the old way to find the foreground tab was to force a tab into the foreground and see what happened.

The test writes itself: read the foreground, hammer a different tab with ordinary commands, read the foreground again.

foreground BEFORE: https://www.reddit.com/notifications
driving OTHER tab:  https://www.reddit.com/r/LocalLLaMA/comments/...
ran 5 CDP commands (evaluate, layout, readyState, getDocument, screenshot)
foreground AFTER:  https://www.reddit.com/notifications

RESULT: focus did NOT move.

Including a screenshot, which is the operation people most often blame. Focus didn’t budge.

My focus mutex was solving a problem that had been fixed three months earlier. The proxy would have been the same mistake with more code.

A bug in my first version of that test is worth mentioning, because it nearly gave me a false pass: /json/list returns page targets, while Target.getTargets with a tab filter returns tab targets. Same tab, different IDs. My “pick a tab that isn’t the foreground” check compared a page ID against a tab ID, never matched, and cheerfully “tested” the foreground tab against itself. Compare by URL, or map the two ID spaces properly.

🔒The fix people reach for that doesn’t work

The obvious idea is one global lock: an agent grabs the browser, does its thing, releases. openclaw#40114 has a good critique — it serializes everything and destroys the concurrency you wanted in the first place.

If you do need coordination, lock the narrowest thing. Reads, DOM queries, JavaScript-driven form fills and fetch all work fine in a background tab, simultaneously, forever. The only genuinely singular resource is the foreground, and only if your stack still steals it.

What I’d tell you to do

  1. Turn on page-ID routing and give each agent its own tab. This is the fix. Everything else is downstream of it.
  2. Before building anything for focus stealing, test whether you have it. Read the foreground with embedderData, drive a different tab, read it again. Thirty lines.
  3. Keep your tooling current. I nearly built infrastructure for a bug that a version bump would have handled — and in my case, that a version I already had did handle.
  4. When automation behaves erratically, check whether something else is driving the browser before you blame the website. That hour cost me more than every other mistake combined.

The honest summary is that two agents in one window is a solved problem in 2026, and I spent a morning proving it the hard way.

🧪Three tests, so you don’t have to take my word for it

After turning the flag on I went back and checked the three things that actually determine whether this works, rather than assuming the config was enough.

1. Do tabs stay isolated? I wrote a marker into one tab, wrote a different marker into another, then went back and read the first.

tab A (news.ycombinator.com)  ->  window.__agentMarker = 'AGENT_A'
tab B (github.com)            ->  window.__agentMarker = 'AGENT_B'
re-read tab A                 ->  'AGENT_A'   leaked: false

No crosstalk. Each tab addressed by ID keeps its own state.

2. Does focus move? Read which tab is foreground via embedderData, fire five ordinary commands at a different tab including a screenshot, read the foreground again.

foreground BEFORE: nicedreamzwholesale.com/...
driving OTHER tab: ineedhemp.com/wp-admin/...
ran 5 CDP commands (evaluate, layout, readyState, getDocument, screenshot)
foreground AFTER:  nicedreamzwholesale.com/...

RESULT: focus did NOT move.

3. Will a background tab accept a write? This is the one that matters most in practice, because it decides whether an agent can do real work without stealing your window. I filled a form field in a tab that did not have focus:

filled: background_fill_test
cleared

It took the value. So a JavaScript-driven fill works fine in the background, and the only thing genuinely needing the foreground is a trusted click or keystroke, which most automation never needs.

The script is here if you want to run it against your own setup: focus_probe.py. Thirty lines, one dependency, answers the question in about a second. Worth running before you build anything to work around a bug you might not have.

What I did not test

All three of those were one agent driving several tabs. I have not run two separate agent processes hammering the same browser at the same moment. The shared-pointer problem that caused the original mess is definitely gone, but “two processes at once” is inference from the design here, not something I sat and watched. Worth saying plainly rather than letting the piece imply more than it earned.

And one thing no flag will fix: every agent shares cookies and login state. They are all the same logged-in you, on every site, with the same session and the same rate limits. In my case two agents spent one account’s daily posting budget without either of them knowing. That is not a browser problem, but it will bite you in the same afternoon.

🪟Same window, different tabs, works today.

Whose work this stands on

Almost everything in this piece is other people’s findings. I just hit the problem and went looking.

  • Browserbase — documented the Chrome 150+ embedderData API and named the two focus-stealing workarounds everyone was using. That post is what let me test rather than assume.
  • OrKoN and the chrome-devtools-mcp maintainers — fixed the focus-stealing bug in May and said so clearly in the issue. Reading that close is what stopped me building something unnecessary.
  • agent-browser — the background-mode proposal, and the write-up of why Target.createTarget with background: true is the right primitive.
  • openclaw #40114 — the critique of a single global browser lock, which is why this ends with “lock the narrowest thing” instead of the obvious wrong answer.
  • Puppeteer PR #14922 — the actual fix.

If your work is listed here and you’d like the wording changed, write to me and I’ll fix it.

Leave a Comment

Your email address will not be published. Required fields are marked *

Shopping Cart
Scroll to Top