co browser
Drive one persistent, logged-in browser from the shell — and let several AI agents share it without stepping on each other's pages.
Quick Start: co browser go_to news.ycombinator.com opens a real browser. The next command drives the same window — logins persist.
Quick Start (60 seconds)
The browser stays open between commands — one shared session — so cookies and logins persist from one command to the next until you close. The first run opens a window you can log into once; every later run reuses that session.
Two Ways to Drive It
Direct functions
co browser go_to x.comDeterministic, instant, free (no LLM): go_to, get_text, click_element_by_selector, take_screenshot, … Run co browser help for the full list.
Natural language
co browser do "find the cheapest flight"An AI agent operates the same live browser and figures out the steps itself.
Output contract: stdout = data, stderr = errors. Exit code is 0 on success.
Why Use This
An automation script that spins up a fresh headless browser every run throws away your logins and pays the launch cost each time. co browser keeps one real browser alive behind a small daemon, so:
- You log into a site once; subsequent commands are already authenticated.
- A shell script (or an AI agent) can issue many quick commands against the same page.
- Multiple agents can work in the same browser at the same time — each in its own tab.
One Task = One Tab
The browser has tabs, and the rule is simple: one task uses one tab. Solo use needs no ceremony at all — bare commands run on the shared main tab:
Running a distinct task (or a second agent)? Give it its own tab:
-t <tab> uses the exact same grammar for direct functions and do. A bare command (no -t) always means the main tab.
Several Agents, One Browser
When two agents share the browser, the daemon makes sure they never silently drive the same page. If a second agent runs a bare command while another is mid-task on main, it fails loudly and is told exactly what to do instead:
This error is the documentation — an agent that has never read these docs learns the whole lifecycle from the message it gets on its first collision. The same guard protects named tabs and refuses tab close of a tab another agent is using. A claim lasts ~2 minutes from the tab's last command; once it expires the tab is free for anyone to take over.
The board
See who is running what, right now:
tab ls --json returns the same board as JSON for scripting.
Identity
The daemon needs to know who you are to attribute tabs and enforce the guard. Set CO_WHO=<name> to name yourself (recommended for scripts) — Claude Code sessions are identified automatically. An anonymous caller can still use the browser, but gets no contention protection.
Exit Codes
co browser returns structured exit codes so an orchestrator can branch without parsing prose:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | the action failed (e.g. selector not found) |
| 2 | usage error (bad flags, empty -t, tab misuse) |
| 3 | unknown tab (-t names a tab that was never tab opened) |
| 4 | tab busy (another agent is mid-task on that tab) |
Command Reference
Everyday functions
co browser help prints the live list; these cover most sessions:
Function arguments follow the shell: positional args in order, options as --flag=value (e.g. take_screenshot --full-page=true). Calling a function with the wrong arguments returns its usage line so a script (or agent) can self-correct.
do — Natural Language
do hands the same live browser to an AI agent that sees the page and works out the steps itself — clicking, typing, scrolling, reading — until your instruction is done:
Describe the end state you want ("download the June invoice PDF"), not the steps. do costs LLM calls and is slower than direct functions — use functions for anything deterministic, do for judgment. While a do runs, the daemon is busy: other commands queue behind it (or exit 4 if they target its tab). This path uses managed keys — run co auth once if you see an authentication message.
Visible or Headless
The default is a visible window — co browser go_to example.com opens real Chrome on your screen, which is what you want for logging in once or watching an agent work. Add --headless to run without a window:
The choice is made by whichever command starts the daemon and sticks for the daemon's lifetime — every later command reuses the same browser regardless of its own flags (co browser status shows headless=true/false). To switch modes, co browser close and let the next command relaunch.
Best Practices
- Solo work: just use bare commands. Don't reach for
-tuntil a second agent or a second concurrent task actually exists. - Concurrent agents: each
tab opens once, adds-t <name>to every command (includingdo), andtab closes when finished. SetCO_WHO. - On an exit-4: don't retry the same bare command — open your own tab (the error tells you how). Two agents on one page corrupt each other's navigation.
- On an exit-3:
tab openthe name first, then target it — a tab must be registered before-tcan drive it. - Scripting:
TAB=$(co browser tab open --for "job")captures the tab name (that's the only thingtab openprints to stdout); branch on the exit code, and readtab ls --jsonto see the shared state.
How It Works
A small daemon owns the one browser and listens on a Unix socket (macOS/Linux) or a named pipe (Windows — native since v1.2.1, no WSL); each co browser … invocation is a short-lived client that sends one request and prints the reply. Every terminal on the machine talks to the same daemon — there is one browser, one board, no matter where you type. The daemon serializes commands, tracks per-tab ownership, and keeps the browser alive between commands. It starts automatically on first use and exits when you close it.
The daemon records its pid next to the socket, so a daemon that is merely busy (a long do holding the single-threaded loop) is never mistaken for a dead one: clients wait up to ~15s for it to come free and then say so ("daemon is busy"), instead of spawning a rival daemon over a live browser. Startup itself is race-proof: a kernel lock makes two terminals' simultaneous first commands elect exactly one daemon — the loser exits and its command is served by the winner.
The browser runs on Patchright, a stealth-patched, API-compatible Playwright fork. Setup is zero-touch since v1.2.1: desktop Chrome is auto-detected and preferred, and if no browser exists the first page-driving command auto-installs chromium (per-user, no admin rights). To install manually instead:
Troubleshooting
"Where is my browser window?"
The default is a visible window; if co browser status says headless=true, some earlier command started the daemon with --headless. Run co browser close, then rerun without the flag.
"tab 'X' is in use by …" (exit 4)
Another agent is mid-task there. Open your own tab (the error shows the three commands). A crashed agent's claim expires on its own in ~2 minutes.
"Chrome failed to start"
Usually running over ssh/cron without a desktop session (start from a logged-in Terminal, or use --headless), or a leftover Chrome still holds the profile. The full launch log is in ~/.co/browser.log.
"daemon is busy" after ~15s
A long do is holding the single-threaded daemon. Wait for it, or find the culprit with co browser status once it frees up.
Nuclear option
Kill the daemon and let the next command start fresh (logins survive: they live in the profile, not the daemon):
State locations — profile (cookies/logins): ~/.co/browser_profile/ · daemon log: ~/.co/browser.log · socket: $TMPDIR/co/browser.sock (plus .pid/.lock beside it).
See Also
- •
co browser help— the live list of every function you can call directly - • co auth — managed keys for the
doagent - • BrowserAutomation — the browser tools used in your own agents
- • Browser agent — full browser automation in code (
co create --template browser)
ConnectOnion