Creates ~/.browser-use/config.json with {} if it doesn't exist,
with 0600 permissions. Ensures config file is always present
regardless of install method.
After diagnostics, doctor now displays config section showing all
CONFIG_KEYS values: api_key (masked), profile ID, proxy, timeout.
Uses get_config_display() from config module. Includes docs link.
cloud connect reads cloud_connect_proxy and cloud_connect_timeout from
~/.browser-use/config.json. Recording always enabled via enableRecording
default on CreateBrowserRequest. No CLI flags — edit config for custom
settings, use cloud v2 REST for full control.
CLIBrowserSession._provision_cloud_browser now reads the env var and
overrides CloudBrowserClient.api_base_url before provisioning. This
keeps the change CLI-only without modifying library code.
Single env var to override the cloud API base URL for all versions.
Per-version overrides (BROWSER_USE_CLOUD_BASE_URL_V2/V3) still take
precedence for backward compatibility.
cloud connect now works with no flags. On first use, creates a
"Browser Use CLI" profile via the Cloud API and saves the ID to
config.json. Subsequent connects reuse it (validates on each call,
recreates if deleted).
Removed --timeout, --proxy-country, --profile-id flags and their
plumbing through daemon/sessions. Power users who need custom browser
settings use cloud v2 POST /browsers directly.
These were duplicates of tab switch and tab close with separate
lock-checking and focus-resolution code paths. Having one way to
do each thing reduces maintenance surface and avoids isolation bugs.
Default close-tab and tab-close paths used session_manager.get_focused_target()
which returns Chrome's globally focused tab. In multi-agent mode this lets
one agent close another's tab. Now uses agent_focus_target_id first, falling
back to global focus only in single-agent mode.
Also: fresh daemon spawn now uses phase-aware state file waiting (15s) instead
of fixed 5s socket polling, consistent with ensure_daemon's probe logic.
- ensure_daemon now phase-aware: waits for initializing/starting/
shutting_down with staleness timeouts, errors on unhealthy sessions
- _close_session only cleans files after confirmed PID death
- _handle_sessions won't delete live daemon's files on stale terminal state
- _probe_session uses socket_pid for split-brain PID resolution
- _is_daemon_process works on Windows (wmic)
- Updated + added robustness tests (codex-authored)
Replace scattered PID/socket/process checks with _probe_session() that
reads the state file, reconciles PIDs, checks liveness, and probes the
socket without deleting anything. Callers decide cleanup policy.
Adds _is_pid_alive, _is_daemon_process, _terminate_pid (cross-platform
with SIGKILL escalation), _close_session (shared by close and close-all).
sessions command now shows phase column. close and close-all both handle
orphaned daemons via SIGTERM fallback. close polls for PID disappearance
up to 15s before giving up.
Daemon now writes <session>.state.json at each lifecycle transition
(initializing → ready → starting → running → shutting_down → stopped/failed).
All shutdown triggers funneled through _request_shutdown() to prevent
double cleanup. Startup rollback on failure cleans up browser resources.
Also fixes cloud browser leak: CLIBrowserSession.stop() now explicitly
stops the remote browser via API instead of just disconnecting the
websocket.
Daemon ping response now includes PID for split-brain resolution.
When the daemon's socket is unreachable but the PID file references a
live process, close now sends SIGTERM directly instead of printing
"No active browser session" and leaving the daemon running forever.
Sockets without a corresponding live PID file were never cleaned up
because cleanup was only triggered per-session on next use. Add a
glob pass in _handle_sessions to remove any .sock file that doesn't
match a live session.
Concurrent agents could interleave focus swaps on the shared
BrowserSession, corrupting each other's state. Wrap the entire
swap-execute-restore cycle in _dispatch_lock and separate the
tab-ownership path from single-agent mode.
CDP input gesture simulation doesn't work in --connect mode (external
Chrome). Switch to window.scrollBy via Runtime.evaluate which works
regardless of connection mode.
cloud connect was silently reading credentials from the library's
~/.config/browseruse/cloud_auth.json, bypassing cloud login/logout
entirely. Now ensure_daemon injects the CLI config's API key into the
daemon subprocess env, so all cloud commands share a single auth source.
- Remove event bus tab listeners from daemon — track tabs directly
- Remove dead event bus fallback branches from commands/browser.py
- Replace SwitchTabEvent/CloseTabEvent dispatches with direct CDP calls
- Update python_session.py to use ActionHandler instead of event bus
- Add JS dialog handler (alert/confirm/prompt) to CLIBrowserSession
- Surface auto-dismissed popup messages in state output
- Only dummy EventBus() for watchdog constructors remains (unavoidable)
CLIBrowserSession.start() now handles all three browser modes without
loading watchdogs or the event bus:
- --connect: CDP URL already known, calls connect() directly
- Managed Chromium: launches browser via LocalBrowserWatchdog._launch_browser()
(instantiated as plain object, not registered on event bus), then connect()
- Cloud: provisions via CloudBrowserClient, then connect()
All modes converge at connect() for lightweight CDP setup. Also adds
process cleanup in kill() for managed Chromium, and is_cdp_connected
property for daemon watchdog polling.
Subclass BrowserSession as CLIBrowserSession that calls connect()
directly instead of start(). Skips all 13 watchdogs and event bus
handler registration. Actions execute via ActionHandler which calls
DefaultActionWatchdog methods directly and DomService for DOM snapshots.
- CLIBrowserSession.start() → connect() only (CDP + SessionManager)
- CLIBrowserSession.stop() → close websocket directly (no BrowserStopEvent)
- CLIBrowserSession.kill() → Browser.close + disconnect
- ActionHandler wraps DefaultActionWatchdog for click/type/scroll/keys/etc
- DomService called directly for state (no DOMWatchdog)
- Monkey-patches _enable_page_monitoring to no-op after initial connect
- Disables auto-reconnect (_intentional_stop = True)
- Falls back to event bus path if ActionHandler is not available
- `browser-use register` assigns numeric agent index for --connect mode
- `--connect <index>` requires explicit agent index (no more bare --connect)
- `tab list` shows all tabs with lock status per agent
- `tab new [url]` creates a new tab without visually switching
- `tab switch <index>` changes agent focus without activating Chrome tab
- `tab close <index> [index...]` closes multiple tabs in one command
- Agent registry in ~/.browser-use/agents.json with 5min expiry
- Improved error messages guide agents to register or use their own tab
- Session lock prevents double BrowserSession creation on simultaneous connect
- Updated SKILL.md with register workflow and tab commands
Multiple agents can share one browser via --connect without interfering
with each other. Each agent registers with `browser-use register` to get
a numeric index, then passes it with `--connect <index>` on every command.
- Tab locking: mutating commands (click, type, open) lock the tab to the
agent. Other agents get an error if they try to mutate the same tab.
Read-only commands (state, screenshot) work on any tab.
- Agent registry: agents.json tracks registered agents with timestamps.
Expired agents (5min inactive) get cleaned up automatically.
- Session lock: prevents double BrowserSession creation when two agents
connect simultaneously.
- Focus swap: daemon swaps agent_focus_target_id and cached_selector_map
per-agent before each command, so element indices are isolated.
When the install script is executed via `curl ... | bash`, it always
runs in a bash process, so $BASH_VERSION is always set regardless of
the user's actual shell. This causes PATH to be written to ~/.bashrc
even for zsh users, making the `browser-use` command unavailable.
Use $SHELL (the user's login shell) instead of $BASH_VERSION/$ZSH_VERSION
to detect the correct shell RC file.
Fixes#4440
If list_chrome_profiles() returns empty (permissions, corrupt Local State,
first-run Chrome), skip resolution and use the profile value as-is instead
of always raising RuntimeError.
When ping fails but daemon is alive, return instead of trying to spawn
a second daemon. The old daemon holds the socket so the new one can't
bind, causing silent config mismatch.