Skip to content

The research browser: what works, what doesn't (2026-08-09)

Written at a stopping point mid-investigation. Read this before touching the browser plumbing — it records what was measured, not what was assumed, because two plausible theories were wrong today and each cost real time.

What works right now

Agents launch their own browser via the inherited user-scope playwright MCP server. This is the configuration that has worked all week and was re-verified at 23:24 (a 3-minute three-source login probe, all origins reached). run_agent.py only overrides the MCP config when a shared endpoint is actually up; with none running, agents fall back to this and it works.

But it does not carry a login between runs — see session preservation below. Each run gets a fresh browser, so 14ers and LoJ start logged out every time. Fine for a one-off report with a human present; not viable for an unattended pass.

Nothing needs to be running for a build to start. Do not start a shared browser out of habit.

What does NOT work: attaching to a shared browser

scripts/browser_session.py --start runs one long-lived Chrome with a CDP port, and optionally a long-lived Playwright MCP server attached to it. Agents pointed at either transport hang on their first browser tool call.

Bisected with run_agent.py --debug:

probe result
prompt using no tools returns in 0.1m — startup and MCP config are fine
initialize over HTTP to the shared server instant, valid response — server is healthy
one browser_navigate call hangs for the entire timeout, no output, no server log

So the fault is the server-to-Chrome link when a browser tool fires, not the agent, not the MCP handshake, and not the config schema. The leading (untested) explanation is that Playwright wants a fresh browser context and a persistent Chrome profile over CDP will not give it one.

Theories that were tested and disproved — do not retry them:

  • Stale internal chrome:// targets stall the connect. --tidy found zero of them while the hang persisted.
  • The long-lived MCP server caused it. The first hang predates that server starting.

Why anyone wanted attach: session preservation

The three research logins live in ONE Chrome profile. Playwright takes an exclusive lock on that profile when it launches, so a stale server from an earlier session starves a headless agent — on 2026-08-09 that cost a 45-minute silent timeout. The obvious remedy, closing the browser to free the lock, kills session-only cookies and logs you out. Releasing the lock and staying logged in are mutually exclusive under the launching model.

"Remember Me" does NOT fix this (Kyle, 2026-08-09, correcting me): on these sites it only pre-fills the username — it does not stop the session expiring. An earlier version of this page recommended it as the solution. That was wrong, and the mistake matters, because it made a blocking problem look optional.

What is actually true: peakbagger sets a genuinely persistent cookie and survived every restart today. 14ers and LoJ are session-only and were logged out by every browser restart, including the one the launching model performs on every agent run.

So a long-lived browser is required, not a nicety — under the launching model, an 81-report pass would need a human login before essentially every report. Either attach has to work, or the session cookies have to survive a restart some other way.

Next thing to try (untested): Chrome persists session cookies across restarts when "Continue where you left off" is set (session.restore_on_startup = 1 in the profile's Preferences). If that holds for this profile, the launching model becomes viable on its own and the attach problem stops blocking. Test it before investing further in attach.

Diagnosing

scripts/browser_session.py --status    # endpoint up/down, pid, and WHO holds the profile
scripts/run_agent.py … --debug         # MCP logs into the run record's stderr

--status names the state that produces a silent hang: a client holding the profile with no endpoint to attach to. run_agent.py now refuses in seconds in that case rather than blocking, and a timed-out agent keeps whatever it managed to say in its run record.

External, not ours

listsofjohn.com was down at 23:24 — Cloudflare 522/525 from the origin. A sweep cannot run while that is true, and an out:true probe during an outage is not a login signal. Re-probe before concluding anything about LoJ.