Provision named devices and let a background daemon keep them running across reboots — no folder required, no terminal tied up.
Two ways to run devices
The folder model is great for a quick capture: cd somewhere, run bmc start, Ctrl-C when done. But for devices you want to keep around — named, supervised, restarting after a reboot — there's the daemon.
A named device lives in a per-machine registry, maps to a Chrome binary, and is launched and supervised by a background daemon. You manage them by name from anywhere, without cd-ing into a folder.
Bring the daemon up
Run bmc with no arguments:
bmcThis starts the daemon in the background (releasing your terminal) and lists this machine's named devices. Run it again any time — it never starts a second daemon, so it's safe to repeat.
To stop the daemon (and every running device under it):
bmc stopProvision a named device
The interactive way is bmc setup — a small terminal UI to add, edit, rename, and remove named devices, mapping each to a Chrome binary (it auto-detects your installed browsers):
bmc setupOr do it directly:
bmc create "Staging" # auto port + profile
bmc create "Staging EU" --from "Staging" # clone binary/flags onto a fresh port + profile
bmc create "Canary" --chrome /path/to/chrome # pin a specific binarybmc create provisions the device (pairs its device JWT, allocates a port and profile) but does not start it.
Start, stop, list
bmc start "Staging" # launch the named device's Chrome via the daemon
bmc stop "Staging" # stop just that device
bmc ps # list named devices + which are running
bmc ps --json # machine-readablebmc start "<name>" is the same command you'd use for a folder device — when <name> matches a registered named device (and the daemon is up), it's routed to the daemon instead of creating a folder device. bmc ps works even when the daemon is down: it shows the registry, all marked stopped.
[bmc] daemon: running
NAME STATE PORT UUID
Staging running 9301 8f3c…
Canary stopped 9302 1a7e…Start automatically at login
Wire the daemon to launch when you log in, and start it now:
bmc autostart # install the supervisor + start the daemon under it
bmc autostart --takeover # same, adopting a daemon that is already running (its browsers stay up)
bmc autostart --linger # also keep it running after logout (where supported)
bmc autostart off # remove the supervisorThe mechanism is per-OS:
- macOS — a launchd LaunchAgent in
~/Library/LaunchAgents, tied to your GUI login session.--lingeris a no-op here (surviving logout would need a root LaunchDaemon), so a machine that must come back on its own after a reboot should auto-login that user. - Linux — a systemd
--userunit.--lingerrunsloginctl enable-lingerso the daemon survives logout. - Windows — a Scheduled Task that runs at logon.
--lingeris a no-op.
Note:
bmc autostartonly wires up the daemon;bmc autostart offremoves that. To bring the daemon up or down by hand, usebmcandbmc stop. Re-runningbmc autostarton an installed unit is safe — it is idempotent and never restarts your browsers.
The supervisor: crashes, updates, reboots
From bmc 85 the login agent is a real supervisor (KeepAlive on macOS / systemd), not just a launcher. What that buys you:
- The daemon comes back on its own. Whatever ends it — a crash, a
kill -9, the hot recycle abmc updateperforms, a manual unload — the supervisor relaunches it within ~10 s. The relaunched daemon re-adopts every browser that is still running instead of restarting them. - A daemon exit never takes your browsers with it. Every device's Chrome runs in its own process group, so a supervised daemon going away is invisible to the pages open in your devices. A hard-killed device child that left an orphan Chrome behind is reaped before its device is relaunched, so two devices can never end up attached to the same Chrome.
- Devices are supervised per flag. A device with autostart on is boot-started and crash-relaunched with backoff (2 s → 60 s, reset after 5 min of stable uptime); a device with autostart off is left exactly as you left it.
bmc daemon --foregroundruns the same supervisor loop in your terminal — what the autostart unit runs — for a one-off session or for debugging.
After a reboot there is nothing to do: at login the supervisor starts the daemon, the daemon pairs its host record and boot-starts every autostart device (seven devices come up in well under ten seconds). Devices without autostart stay stopped until you bmc start "<name>" — or flip them with bmc edit "<name>" --autostart on so the next reboot brings them too. Verify with bmc ls: the FLAGS column shows auto for autostart devices and ctl-only for capture-off ones; every autostart device should read running. A device that shows stopped with auto means the supervisor's crash ladder gave up — an operator bmc restart "<name>" lifts the hold. Because both flags live on the device's settings row, the dashboard's Automation tab and the MCP tools set_device_autostart / set_device_capture_enabled show and flip the same state, and bmc ls reflects a remote flip at once.
Window mode: front, minimize, and running out of sight
A device's Chrome is a real, fully-windowed browser — its window's on-screen state is what
windowMode controls (settings_device.data.windowMode: "visible" or "minimized"). Nothing
about the page changes; a site can't tell the difference. This is deliberately not headless
mode — see why below.
Class default. A row that says nothing resolves by device class: usage-probe devices and any
capture-off (control-only) device start
minimized (off-screen at launch, then moved out of the way once Chrome attaches) — nobody is
meant to be watching them, so they never fly across your screen. Every other device — your own
remote-control browser — starts visible, unchanged from before windowMode existed.
Bring one to the front, or send it back:
bmc front "Staging" # raise the window, front the tab, title it "[Staging] …"
bmc minimize "Staging" # send it back off-screenbmc front opens a short-lived CDP connection straight to the device's own local port, raises the
window (Browser.setWindowBounds), brings its tab to front, and titles the tab [<name>] … so
it's identifiable among every other bmc window on screen — then closes that connection. The
device's own long-lived capture session is never disturbed. The Ink device list (the live bmc
console) offers the same as hotkeys on the selected running device: f (front) / m
(minimize).
Reachable remotely. front/minimize dispatch over the same bmc.daemon.* host-command wire as
start/stop/create — the same wire proven reachable below
— gated by the same
Browser Control opt-in
as every other remote command. That makes two more surfaces possible on top of the identical wire:
- The dashboard Devices panel offers Bring to front / Minimize in a
bmcdevice's row action menu, plus a Window mode toggle in its settings (persists the row — same effect asbmc edit, without a CLI). - MCP exposes both halves as separate tools:
focus_device_windowraises or minimizes a running device's window right now ({ device_uuid?, deviceName?, device, action: "front"|"minimize", confirm: true }—device_uuid/deviceNameselect the host machine,devicenames the target device on it) without touching the row, andset_device_window_modepersists the setting on a named device ({ device_uuid?, deviceName?, windowMode: "minimized"|"visible", confirm: true }) — the same rowbmc editand the dashboard toggle write. Full argument reference: MCP tools → Named CDP device daemon.get_device_statusreports the persisted setting back aswindowMode(best-effort;nullmeans the class default applies), andlist_cdp_instancesreports each running device's live on-screen state aswindowState: "normal"|"minimized".
Headless: one launch vs. persistent
There is a second, unrelated way to hide a bmc browser: real Chrome headless mode
(--headless=new) — no window exists at all, not even off-screen, which also removes the whole
compositor/GPU/audio pipeline. bmc deliberately does not default any device class to it: some
origins (claude.ai among them) detect headless Chrome and serve a Cloudflare interstitial instead of
the real page, so a device that must sign in and stay signed in is safer minimized (a genuine
window, above) than headless. Headless stays
available, opt-in, per device — settings_device.data.headless: true — for a device whose origin
doesn't gate on it.
That persistent row is a different thing from the one-shot override on bmc start:
bmc start "cu-claude6" --headless off # THIS launch only, visibly — e.g. to re-sign in--headless on|off (same on/off spelling as --capture/--autostart) overrides a single
launch — it is never written to the row or the local mirror, so the very next natural start or
restart reverts to whatever the row/mirror/class-default already said. The MCP mirror is
start_device's optional headless argument, forwarded the same way. Use the one-shot flag to sign
an expired account back in with eyes on the page, then let the device revert to its normal setting
on its own — never bmc edit a row just to undo it afterwards.
Is it really answering? The command wire
A daemon can be up and unreachable at the same time, and until bmc 86 nothing could tell you
apart. The heartbeat that keeps a machine's row fresh travels over plain HTTP; the remote commands
you send from the dashboard, MCP or BusyBro travel over a Realtime channel. Those are two
independent paths. On 2026-09-05 the second one broke on a loaded machine and the first one kept
reporting health for hours: a fresh heartbeat, serving: true, twelve browsers online — and not one
command arriving. serving had only ever meant "the local socket is bound".
So the daemon now proves the wire instead of assuming it. Every so often it sends a real command
to itself through its own channel and requires its own result back, which exercises receive,
dispatch and send in one round trip. The verdict is reported as commandWire:
| State | What it means |
|---|---|
| up | A round trip completed inside the freshness window. Commands are reaching this machine. |
| degraded | Recent failures behind a still-recent success — the wire is flapping. |
| down | No round trip has completed in the window. Commands are not arriving, whatever else looks green. |
| unknown | No probe has completed yet, or the daemon is older than the check. Not a pass. |
unknown is deliberately not a friendly grey: a panel that cannot say whether a machine answers has
to say so out loud, which is exactly what the outage taught. The dashboard reads the same four words
— see the daemon panel — and a machine reporting
serving: true with a down wire is shown as down.
Two things follow from the same fix:
- Broken channels rejoin themselves. Every channel is watched for the error, timeout and closed states that used to be discarded silently, and a broken one is rebuilt with a bounded, jittered backoff (1 s → 30 s). A healthy resubscribe cancels a pending rejoin, so a working channel never churns.
- A wedged daemon restarts out of band. Five consecutive failed round trips and the daemon exits; the login supervisor starts a clean one within ~10 s. That works precisely because it depends on neither the wire nor the loop that just failed. An unsupervised daemon logs the escalation and stays up instead — exiting into nothing would only make it worse.
Captured traffic survives a refused upload
Ingest can refuse a batch for reasons that have nothing to do with the batch: a rate limit, or the
edge in front of the API answering before the API sees it. Those are now treated as temporary,
retried with backoff, and any Retry-After the edge sends is honoured as a floor.
A batch that still cannot land after its retries is parked on disk in the device's state directory rather than dropped, and replayed oldest-first after the next successful upload, on the idle tick, and across a daemon restart. The spool is bounded (200 files / 64 MB) and a corrupt file is quarantined instead of poisoning the queue. A batch the server rejects as genuinely permanent — a payload it will never accept — is still dropped, because replaying a poisoned batch forever is not durability.
This is new in bmc 87. Before it, a refused upload was a silent data loss.
Autostart per device
bmc autostart installs the supervisor for the daemon itself (a launchd LaunchAgent / systemd --user unit with KeepAlive — the daemon is relaunched within ~10 s whenever it exits). Which devices the daemon brings up on its own is a per-device flag, settings_device.data.autostart:
bmc create "probe" --autostart on # boot-start + crash-relaunch this device
bmc edit "probe" --autostart off # manual only (a running device is NOT stopped)With the literal true the daemon starts the device on boot and relaunches it with backoff (2 s → 60 s, reset after 5 min of stable uptime) whenever it exits on its own. The flag is read from the device's settings row at boot and at every restart decision, so a flip from the dashboard (Autostart on the device's Automation tab) or the MCP tool set_device_autostart lands without a daemon restart — turning it off never stops a running device. bmc ls marks such a device auto.
A device that should always be up but never captured combines this with capture off: --capture off --autostart on.
Folder devices vs. named devices
| Folder device | Named device | |
|---|---|---|
| Created by | bmc start in a folder | bmc create / bmc setup |
| Identity stored in | ./.bmc.json in the folder | per-machine registry |
| Started with | bmc start (foreground, in that folder) | bmc start "<name>" (via the daemon) |
| Survives reboot | no | yes, with bmc autostart |
| Terminal | holds the terminal until Ctrl-C | runs in the background |
Both stream into the same dashboard feed and show up under Devices — they're the same kind of device, just provisioned and supervised differently.
Next
- External proxy & custom Chrome — per-device upstream proxy and binary selection.
- Command reference — every command and flag.