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.
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:
# 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:
{
"cookies": [...],
"localStorage": {...},
"sessionStorage": {...},
"origins": [...]
}
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:
$ 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
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:
$ 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"
When --session is omitted, chrome-use derives a stable per-agent session from supported
runner IDs. Codex tasks use CODEX_THREAD_ID automatically, Claude Code uses
CLAUDE_PID, and other runners can provide AGENT_BROWSER_SESSION_ID — a runner
nobody here has heard of is picked up too, as long as it exports a
<PRODUCT>_SESSION_ID / _THREAD_ID / _CONVERSATION_ID variable.
Failing all of those it falls back to a terminal ID (WT_SESSION,
TERM_SESSION_ID, …), and only a shell with none of them retains the
default session. Run chrome-use doctor and read the
Default session line to see which session this shell resolves to and which variable it
was derived from. To set an explicit shell default, use
AGENT_BROWSER_SESSION=myapp. Close a session with close when you no longer
need it, and use session list to see which sessions are still active:
cu-<repo>-<tag>: <tag> comes from the agent /
terminal id and <repo> is only the cwd basename. Running a command from another directory
used to mint a different name and hit a fresh daemon with no refs ("Unknown ref"). Now, when a live daemon
already carries the same agent tag under another prefix, that session is reused, so an agent keeps its tabs and
refs across cd. Explicit --session <name> / AGENT_BROWSER_SESSION
still win. The "Unknown ref" error now names the session that answered and says whether it has any refs at all
(none: "no snapshot has run in this session; if you snapshotted from another directory or terminal, use
session list and pin with --session"), or lists the ref range it does have.
$ 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
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. Codex task isolation is automatic; elsewhere, give each agent
its own --session <name> or AGENT_BROWSER_SESSION_ID.
--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:
# 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 adopt <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.
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.
Give the tab group a name a person can read
In your real Chrome, the tabs a session opens are collected into a tab group. By default
that group is labelled with the session id
(cu-myproject-fb0742) — a routing key, which tells the person whose browser it
is nothing about what the agent is doing in it.
$ chrome-use session name "🔎 track a parcel"
✓ tab group for session 'cu-myproject-fb0742' is now 🔎 track a parcel
$ chrome-use session name # show the current one
$ chrome-use session name --clear # back to the session id
Set it before you open tabs. The label is fixed when the group is created, so setting it later only affects tabs opened from then on; already-open tabs need the extension to rename the group, which requires a recent enough build. The command says which of the two happened, so it never implies the new name applied everywhere.
Short, task-relevant, and led by a neutral friendly emoji reads best in the tab strip; use 🔎 when unsure. Renaming affects no routing — the session id remains the identity, and the label is presentation only.
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:
# List running session daemons (with relay status)
$ chrome-use daemon status
# Stop one daemon gracefully (defaults to the current session)
$ chrome-use session stop [name]
# Stop every active session daemon gracefully
$ chrome-use session prune
# Kill all session daemon workers
$ chrome-use daemon restart
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)
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:
$ 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
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
The auth credential store, OAuth, passwords piped from stdin — log in securely, then save the session.
The extension connection path: reuse your already-logged-in session, each --session getting its own colored tab group.
Set up chrome-use, the extension, and the agent skill to get the session flows above running.