← Blog

BLOG

Browser Use CLI: Run AI Agents on Hosted Chromium

A practical guide to the browser use CLI for AI agents: install, connect to hosted Chromium over CDP, and run Playwright or Puppeteer against a remote runtime.

September 15, 20269 min readRemote Browser

# Browser Use CLI: Run AI Agents on Hosted Chromium

A browser use CLI is a command-line tool that lets an AI agent or a developer drive a real browser — navigate, click, type, extract — without hand-writing a full automation script each time. The catch most teams hit is that the CLI still needs a browser to talk to. Running that browser locally works for a demo and breaks in production: no isolation between runs, no persistent profiles, no way to watch a session live, and no clean path to scale past one machine.

This guide covers what a browser use CLI actually does, where local execution stops being viable, and how to point a CLI at hosted Chromium over the Chrome DevTools Protocol (CDP) so the same commands run against a remote runtime.

What a browser use CLI actually is

The term covers two related things, and conflating them causes confusion:

  1. Agent-facing CLIs — tools like agent-browser that expose browser actions (open, click, snapshot, extract) as shell commands an LLM can call. The agent reasons in text; the CLI translates that into browser operations.
  2. Framework CLIs — the playwright and puppeteer command-line entry points that install browsers, run test files, and open debugging sessions. These are the substrate an agent CLI usually sits on.

Most production stacks use both. The agent CLI handles the reasoning loop; Playwright or Puppeteer handles the actual protocol work. The question that determines your architecture is not *which CLI* but *which browser the CLI connects to*.

Why local browsers stop working

A local Chrome launched by playwright chromium launch is fine until you need any of the following:

  • Session isolation. Two agents running concurrently on one machine share a profile directory unless you manage userDataDir carefully. Cookies, localStorage, and auth state leak between runs.
  • Persistence across restarts. A login session that should survive a worker restart needs a profile that lives somewhere durable, not in a temp directory that gets cleaned up.
  • Live visibility. When an agent fails at step 14 of 20, you want to see the DOM state at that moment, not reconstruct it from logs.
  • Horizontal scale. Ten concurrent agents on a laptop is a resource fight. Ten concurrent agents across a fleet needs a browser layer that isn't tied to one host.
  • Consistent environment. Your laptop's Chrome version, extensions, and OS differ from the CI container's. Reproducing a bug means reproducing the environment first.

None of these are fatal individually. Together they're why teams move the browser off the local machine and keep the CLI as the control surface.

Connecting a CLI to hosted Chromium over CDP

The Chrome DevTools Protocol is the wire format. Playwright's chromium.connectOverCDP() and Puppeteer's connect() both speak it, which means any CLI built on either can target a remote browser by swapping a launch call for a connect call.

The shape is the same everywhere:

import { chromium, Browser, BrowserContext, Page } from 'playwright';

// The runtime exposes a CDP endpoint. Treat it as a secret.
const CDP_ENDPOINT = process.env.REMOTE_BROWSER_CDP_URL!;

async function runTask(): Promise<void> {
  // connectOverCDP attaches to an already-running browser.
  // Unlike chromium.launch(), it does not spawn a local process.
  const browser: Browser = await chromium.connectOverCDP(CDP_ENDPOINT, {
    timeout: 30_000,
  });

  // A hosted session usually starts with one context already open.
  // Reuse it rather than creating a new one, or you lose the profile state.
  const context: BrowserContext = browser.contexts()[0]
    ?? await browser.newContext();

  const page: Page = context.pages()[0] ?? await context.newPage();

  await page.goto('https://example.com/dashboard', {
    waitUntil: 'domcontentloaded',
  });

  // Wait on a real signal, not a fixed sleep.
  await page.getByRole('button', { name: 'Export' }).click();
  await page.waitForResponse(
    (res) => res.url().includes('/api/export') && res.status() === 200,
  );

  const rows = await page.locator('table tbody tr').allTextContents();
  console.log(`extracted ${rows.length} rows`);

  // Close the connection, not the browser, if the session is managed
  // by the runtime and should stay alive for the next step.
  await browser.close();
}

runTask().catch((err) => {
  console.error(err);
  process.exit(1);
});

Three details matter more than the rest:

  • `connectOverCDP` vs `launch`. Launch spawns a process you own. Connect attaches to one you don't. Cleanup semantics differ: closing a connected browser may or may not terminate the remote session, depending on the runtime.
  • Context reuse. If the runtime hands you an existing context, creating a fresh one discards the profile — cookies, storage, auth — that the session was set up with.
  • Timeouts. Remote connections cross a network. A 30-second connect timeout is a reasonable default; the Playwright default is shorter and will produce confusing failures on cold starts.

The Playwright CDP documentation is the authoritative reference for connectOverCDP behavior and its Chromium-only constraint.

Local CLI vs hosted runtime: a decision table

CriterionLocal browser + CLIHosted Chromium + CLI
SetupInstall browser binaries per machinePaste a CDP URL
Session isolationManual (userDataDir per run)Per-session by default
Profile persistenceLocal disk, lost on container recycleConfigurable, survives restarts
Live debuggingScreenshots and logsLive viewer on the running session
ConcurrencyBounded by host CPU/RAMBounded by your plan, not your laptop
Environment consistencyDrifts across machinesSame image every session
Proxy / network configPer-machine setupConfigurable per session
Cost modelYour infra, your ops timeMetered — see /pricing
Best fitLocal dev, one-off scriptsAgents, CI, multi-tenant automation

The honest read: local wins on latency and zero marginal cost for a single developer iterating on a script. Hosted wins the moment more than one process, more than one machine, or more than one person is involved.

What to look for in a hosted runtime

Not every remote browser is built for agent workloads. These are the criteria that separate a usable runtime from a thin wrapper around a container:

CDP access, not just an SDK. If the runtime only exposes a proprietary API, you're locked into its abstractions. Raw CDP means Playwright, Puppeteer, and Selenium all work, and so does anything else that speaks the protocol. See Remote Browser for AI agents for how this plays out in agent loops.

Persistent profiles. Agents that log in once and act many times need profile state that outlives a single session. Ask how profiles are scoped and whether they can be reused across sessions.

Live viewer. Debugging an agent that failed silently is the single biggest time sink in browser automation. A viewer that shows the current page state turns a 30-minute log dig into a 30-second look.

Configurable browser settings. Proxy routing, locale, timezone, and viewport should be settable per session. Be precise about what you need: "configurable browser settings" is a real feature, and any additional privacy or anti-detection claims should be verified against the vendor's own documentation before you rely on them.

Usage controls. You need to know what a session costs and be able to cap it. Metering by browser-hour is common; check /pricing for current rates rather than assuming.

Isolation guarantees. One session's cookies must not reach another's. This is table stakes for multi-tenant agents and worth testing explicitly.

Wiring the CLI into an agent loop

The CLI is the interface; the runtime is the execution environment. A typical loop looks like this:

  1. The agent decides on an action in natural language.
  2. The CLI translates it into a browser operation against the connected session.
  3. The runtime executes it in hosted Chromium.
  4. The CLI returns a result — DOM snapshot, extracted text, screenshot reference — to the agent.
  5. Repeat until the task completes or a step budget is exhausted.

Two production concerns fall out of this:

Step budgets. Agents loop. Without a hard cap on steps or wall-clock time, a confused agent burns browser-hours. Enforce the cap in the CLI wrapper, not in the prompt.

Failure semantics. Distinguish "the page didn't load" from "the selector didn't match" from "the agent chose the wrong action." Each needs a different recovery path. A runtime that exposes session state and a live viewer makes this diagnosable; one that only returns a final error string does not.

For a broader look at how remote browsers fit into agent architectures, see Remote web browser and Remote control browser.

Common mistakes when moving a CLI to a remote browser

Treating `connectOverCDP` as a drop-in for `launch`. It isn't. Launch options like args, headless, and executablePath are meaningless when you're connecting to a browser someone else started. Those belong in the runtime's session configuration, not your Playwright code. If you're coming from a launchOptions-heavy setup, expect to move that configuration to the session-creation call.

Creating a new context on every step. This discards the profile and forces re-authentication. Reuse the context the runtime provides.

Ignoring connection lifecycle. A dropped CDP connection mid-task leaves the remote session running. If the runtime bills by browser-hour, orphaned sessions cost money. Close what you open, and check whether your runtime auto-terminates idle sessions.

Hardcoding the endpoint. CDP URLs are credentials. Read them from environment variables, rotate them, and never commit them. A leaked endpoint is a leaked browser.

Assuming Chromium-only features work elsewhere. connectOverCDP is Chromium-only in Playwright. Firefox and WebKit have different connection models. If your agent needs cross-browser coverage, verify the runtime supports it before designing around it.

When a CLI plus hosted runtime is the right call

Use a local browser when you're writing and debugging a script, when latency is critical and the workload is single-tenant, or when the task is genuinely one-off.

Move to a hosted runtime when any of these are true:

  • More than one agent runs concurrently.
  • Sessions need to survive a worker restart.
  • You need to watch a session live to debug it.
  • You're running in CI and want the same environment locally and in the pipeline.
  • You're paying engineers to maintain browser infrastructure instead of agent logic.

The CLI doesn't change in either case. What changes is the URL it connects to — and everything that URL implies about isolation, persistence, and observability.

Start with the documentation to see how sessions are created and how the CDP endpoint is exposed, then check /pricing to model cost against your expected browser-hours. The migration itself is usually a few lines: swap launch for connectOverCDP, move launch configuration into session settings, and stop managing browser binaries on your own machines.