Sessions & Auth

Sessions & Persistence

Log in once and every later run starts logged in; give each agent its own isolated browser so they never fight over tabs. This page covers three things: persisting session state to reuse it across runs, running multiple browsers in parallel with --session, and cleanly resetting the session daemon when it gets stuck.

ℹ️ Know which path you're on
state save / --state / --session-name are for the browsers chrome-use launches itself and for --session isolated browsers — those start with no login, so a state file is how you carry it across runs. Driving your real Chrome over the extension connection needs none of this: the login already lives in your real profile and persists natively, surviving restarts. See Real Chrome.

Persist login state across runs

Log in once, save cookies and localStorage to a file, then load it on later runs to start already logged in — no need to walk through the login form every time. Save with state save; pass --state at launch, or use state load to inject the state into the current session and keep going:

state save / load / reuse
# Log in once, save cookies + localStorage + login state
$ chrome-use state save ./auth.json

# Pass the state at launch — start already logged in
$ chrome-use --state ./auth.json open https://app.example.com

# Or inject saved state into the current session and keep going
$ chrome-use state load ./auth.json
$ chrome-use open https://app.example.com/dashboard

For how to log in cleanly first and then save the session, see Login & Credentials — that page covers the same state save from the login-flow angle.

The state file is a plain JSON document with cookies, both kinds of storage, and an origins list:

auth-state.json
{
  "cookies": [...],
  "localStorage": {...},
  "sessionStorage": {...},
  "origins": [...]
}
⚠️ The state file is plaintext credentials
auth.json holds login-capable cookies and tokens — don't commit it to Git (echo "*.auth-state.json" >> .gitignore), delete it when you're done, and keep it out of any directory that gets packaged and distributed.

Automatic save/restore: --session-name

If you'd rather not save/load by hand, use --session-name (or the environment variable AGENT_BROWSER_SESSION_NAME) — state for a same-named session is saved automatically and restored automatically on later runs:

Automatic save / restore
$ AGENT_BROWSER_SESSION_NAME=my-app chrome-use open https://app.example.com
# Run again with the same name later and state is restored automatically
ℹ️ Cloudflare clearance reuses the same way
The cf_clearance cookie you get from passing a Cloudflare challenge is bound to your IP + User-Agent. For an isolated session, the --session-name save/restore carries it along, so as long as the egress IP and UA don't change, you skip the repeat challenge. For more on login and credentials, see Login & Credentials.

Run multiple browsers in parallel with --session

Each --session <name> is a mutually isolated browser — its own cookies, LocalStorage / SessionStorage, IndexedDB, cache, history, and tabs. Great for testing multi-user flows or scraping in parallel:

Two isolated sessions
$ chrome-use --session a open https://app.example.com
$ chrome-use --session b open https://app.example.com
$ chrome-use --session a fill @e1 "alice@test.com"
$ chrome-use --session b fill @e1 "bob@test.com"

To set a default session for the current shell, use AGENT_BROWSER_SESSION=myapp; when you omit --session, commands land on the default session. Close a session with close when you no longer need it, and use session list to see which sessions are still active:

Default session & cleanup
$ AGENT_BROWSER_SESSION=myapp chrome-use open https://example.com

# Close a specific session
$ chrome-use --session auth close

# List active sessions
$ chrome-use session list
⚠️ Concurrent agents must each use a different --session
Within a session, commands are pinned to the tab you opened (locked by target_id, so a stray tab won't drag your eval/screenshot off target). Two agents sharing the same session (e.g. both on the bare default session) share one daemon and one active tab, and overwrite each other. Give each agent its own --session <name>.
ℹ️ Real multi-agent isolation goes through the extension connection
Real multi-agent isolation needs the extension connection path: each --session gets its own colored Chrome tab group, and sessions never touch each other's tabs. Bare --cdp <port> is not isolated — every session attaches to the same browser's existing targets, and a second session's first open can navigate a sibling session's tab. To run concurrent agents on the same real Chrome, use the extension (each with a distinct --session), not bare --cdp. See Real Chrome.

Grab a specific tab across sessions: targetId adoption

Each session has its own tab group with its own t<N> numbering (the same physical tab is t8 in session A and t1 in session B), so t<N> is not a stable handle across sessions. To grab a specific tab from another session (say a session half-filled a form but its handle died), use the stable CDP targetId:

Adopt a tab by targetId
# Re-sync the live tabs, printing target: <id> on each line
$ chrome-use tab list --full --session B

# Adopt that exact tab — no reload, state preserved
$ chrome-use tab <targetId> --session B

tab list rediscovers the live tab set on every call, so a new session can see tabs opened by other sessions (and re-attached tabs), not just its own. Adopting by targetId lands session B on that stranded tab without reloading it, so the half-filled form survives.

✅ Use --reuse-tab to avoid duplicate tabs
When re-binding, if you open the same entry URL again, add --reuse-tab: if a tab is already showing that URL (matched by origin+path), it switches to it instead of stacking up a new tab.

Reset a stuck daemon state

Each session runs a background daemon worker to hold the page handles. This daemon is also where the boa JS engine lives — the engine runs outside the page, which is why script survives whole-page hard navigations without losing state. If a session starts misbehaving — commands hitting the wrong tab, refs/handles looking stale, or a stale worker left over because you upgraded chrome-use mid-session — just restart the daemon; don't go hunting for the PID with pgrep/kill:

daemon status / restart
# List running session daemons (with relay status)
$ chrome-use daemon status

# Kill all session daemon workers
$ chrome-use daemon restart
✅ restart leaves your Chrome and tabs alone
daemon restart doesn't touch the extension's native messaging bridge (__nm-host), so the relay to your live Chrome stays online — the next command just spins up a clean new daemon against the same browser. It does not close any tab.

Rare case: hand a session to the user (session handoff / resume)

✅ Log in autonomously first — that's the default and the goal
chrome-use is built for the agent to log in by itself: pull credentials from the auth vault / Bitwarden (bwu get … | chrome-use fill … --stdin), read TOTP/2FA codes, drive OAuth, and persist the session (state save / --session-name) so later runs start logged in. Don't hand a login to the user just because it has a password or a 2FA step — solve it. See Login & Credentials.

session handoff is an escape hatch, not a login method. Use it only when a step is genuinely impossible for the agent — an image/behavioral captcha you can't solve, an SMS/authenticator code you have no access to, a hardware-key tap, a bank's "approve on your phone" prompt. Try autonomously first; hand off only as a last resort:

handoff → user acts → resume
$ chrome-use session handoff   # last resort: mark user-owned; tell the user exactly what to do
# … the human does the one thing the agent truly can't …
$ chrome-use session resume    # take control back — ONLY after they confirm they're done
⚠️ The agent is locked out during handoff
While handed off, any browser-driving command on that session is refused (loud error with the exact session resume line), so the agent can't fight the user for the tab. Zero-impact until you call handoff — the agent owns and drives every session by default, autonomous login included. session status shows the owner; session list lists every session with its owner. Don't resume on your own to grab control back — wait for the user to say they're done.

Related pages