Letting agents drive a real browser on the Mac I'm typing on

2026-10-24

Headless Chrome walled over one User-Agent token, macOS dragging 'off-screen' windows back into view, minimized popups that steal your keyboard, and sign-in windows that leaked for days.

I was halfway through a sentence in my terminal when the rest of it went somewhere else. No window had appeared. Nothing on screen had changed. My keystrokes were simply going into a Chrome window I couldn't see, opened by one of my own agents.

That is the strange position you end up in when your agents run on the laptop you're working on. Mine run all day: hourly fleet runs, scheduled jobs, and a reviewer that asks a frontier model for a second opinion in a browser tab. Plenty of those steps need a real, logged-in browser rather than an HTTP client, which means a real Chrome, with a real profile, on my real desktop.

So the problem has two sides. The agent needs a browser that looks like a person's: real profile, real sessions, real user agent. I need it to never show up on my screen and never take my keyboard. Here is what that took, in the order things broke.

1. Headless got walled over one word

Headless Chrome is the obvious way to stay off the screen. On chatgpt.com it sat forever on Cloudflare's "Just a moment..." page.

The usual advice at this point is a stealth stack: fingerprint patches, simulated mouse movement, everything people do to look human. None of it was needed. Headless Chrome puts the token HeadlessChrome in its User-Agent header. That's it. With the same profile, still headless, I sent the exact User-Agent that headed Chrome sends (major version read from the installed binary, reduced to major.0.0.0 the way Chrome itself reduces it), and the challenge page turned into the logged-in composer.

Before you build a stealth stack, diff what your headless browser sends against what a normal one sends. The difference that matters is often the dullest one.

2. macOS will not let you hide a window off-screen

A few flows refuse headless entirely, so I also needed a headed Chrome that is never visible.

My first idea was the classic one: --window-position=-10000,-10000. Put the window somewhere no screen is. macOS doesn't allow that. A window that sits on no display gets clamped onto the nearest one, and my external monitor sits to the left of the laptop. The "hidden" window landed in full view, on the bigger screen, and Chrome un-hid itself on every new page for good measure.

Hiding a window 10,000 pixels to the left, on a desk where the second monitor is on the left. In hindsight.

What works:

3. Hidden pixels are not hidden focus

This was the sentence-eating bug from the top. A minimized window can still take keyboard activation when it's created, and an OAuth popup does exactly that. I would be typing into my terminal and the keystrokes went to Chrome.

The fix is a small focus guard: check the frontmost process every 2 seconds, and hide Chrome only when Chrome is frontmost. Both halves of that rule came from bugs I shipped first:

I didn't trust it until I measured it: sample the on-screen window list every 50 ms while ChatGPT loads. Zero on-screen windows, and Chrome never took the foreground.

4. Sign-in windows leaked for days

Sign-in windows piled up in the Dock for days before I noticed. There were two causes:

Now every page created while attached is tracked, identity-provider pages are closed on teardown, and the shared browser lock sweeps leftover auth popups when it's released. That last part catches jobs that were killed and never got to run their own teardown.

Two traps while writing that cleanup, in case you're about to write one:

A reaper (launchd, every 120 s) also closes any headless or hidden Chrome that has had no DevTools client for 5 minutes.

5. Three modes, and one honest way to ask for help

All of that ended up as three modes behind one call:

Mode Used for On screen
headless everything that works headless never
hidden sites that refuse headless no: minimized, focus-guarded
handover a blocker only I can clear (login, 2FA, CAPTCHA) yes

There is one Chrome per profile. A hidden caller evicts a headless one; a handover window is protected for 30 minutes and is never reaped while I'm using it. An agent that hits a CAPTCHA doesn't guess or retry. It opens a handover window and files a blocker for me.

headlessthe defaultnever on screenhiddensites that refuse headlessminimized, focus-guardedhandovera blocker only I can clearon screen, protected 30 minevictsevictsevictsReaperlaunchd, every 120 s: closes headless or hidden after 5 min idlenever reaped while in use

Arrows point at the mode that gets closed. Orange marks the one mode that puts a window on my screen.

6. And then an agent did it anyway

I should say how much I care about this. Of everything an agent can do wrong on my machine, interrupting me ranks near the top, above being slow and above being wrong in a way I can fix later. You would think that after all this I'd be done with agents taking over my screen. This week an agent working in my own everyday Chrome (not the automation one) hit a page that renders nothing at all while its window is minimized. It wanted the page, so it un-minimized the window. Focus gone, mid-sentence, again.

None of the machinery above was in play, because that browser isn't the automation browser. It is mine. The rule that would have stopped it is the one from section 5: when the only way forward is something visible, that's a handover to me, never a call the agent makes on its own. It's now written down as exactly that, and that step is waiting for me.

What I'd tell someone building this


Thanks for reading. Next up: why my agents got more reliable once I took some steps away from them and turned those into plain scripts. Follow on RSS or X @denzilnelsonai. If you run agents on your own machine, I'd like to hear what they've done to your screen.

— Denzil Nelson