Browser Trace Attach a second, read-only CDP client to a browser session that is already being driven by your main automation. The trace records the full DevTools firehose to NDJSON, polls for screenshots and DOM dumps in parallel, and slices everything into a directory tree that bash tools can search. This skill does not drive pages — it only listens. Pair it with the browser skill, bb , Stagehand, Playwright, or anything else that speaks CDP. When to use The user wants to debug a browser-automation run (failing form, missing element, hung navigation, JS exception). The user has a running automation and wants to attach a trace mid-flight without restarting it. The user wants to split a CDP firehose into network / console / DOM / page buckets. The user wants screenshots + DOM snapshots over time, joined to CDP events by timestamp. If the user just wants to drive the browser, use the browser skill instead. Setup check node --version
require Node 18+
which browse || npm install -g @browserbasehq/browse-cli@alpha which bb || npm install -g @browserbasehq/cli
only needed for Browserbase remote
which jq || true
optional — used only for ad-hoc querying
- Verify
- browse cdp
- exists (it ships in 0.5.0-alpha-a4ca430+):
- browse
- --help
- |
- grep
- -q
- "^\s*cdp "
- ||
- echo
- "browse cdp not in this version — install @alpha"
- How it works
- Every Chrome DevTools target accepts
- multiple concurrent CDP clients
- . Your main automation is one client; this skill adds a second one that only enables observation domains (Network, Console, Runtime, Log, Page) and never sends action commands.
- The tracer has three pieces:
- Firehose
- :
- browse cdp
- streams every CDP event as one JSON object per line to
- cdp/raw.ndjson
- .
- Sampler
-
- a polling loop calls
- browse --ws
screenshot - and
- browse --ws
get html body - on an interval (default 2s).
- --ws
- is one-shot and bypasses the daemon, so it doesn't fight the main automation.
- Bisector
- after the run, bisect-cdp.mjs walks raw.ndjson once, slices it into per-bucket JSONL files keyed by CDP method, and additionally bisects per page using top-level Page.frameNavigated events as boundaries. Quickstart Local Chrome
1. Launch Chrome with a debugger port (any user-data-dir keeps it isolated).
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ --remote-debugging-port = 9222 \ --user-data-dir = /tmp/chrome-o11y \ about:blank &
2. Start the tracer.
node scripts/start-capture.mjs 9222 my-run
3. Run your main automation against port 9222.
browse env local 9222 browse open https://example.com
...whatever the run does...
4. Stop and bisect.
node scripts/stop-capture.mjs my-run node scripts/bisect-cdp.mjs my-run Browserbase remote Two helpers wrap the platform-side bookkeeping: bb-capture.mjs creates or attaches to a session and starts the tracer; bb-finalize.mjs pulls platform artifacts (final session metadata, server logs, downloads) into the run dir at the end. Browserbase ends a session as soon as its last CDP client disconnects. Always create with --keep-alive and attach an automation client before (or together with) the tracer. bb-capture.mjs --new does this for you. export BROWSERBASE_API_KEY = .. .
1. Create a keep-alive session AND start the tracer in one step.
Prints the session id, connectUrl prefix, and a live debugger URL you
can open in a browser to watch the run interactively.
node scripts/bb-capture.mjs --new my-run
2. Drive automation. bb-capture stamped the session id into the manifest.
SID
$( jq -r .browserbase.session_id .o11y/my-run/manifest.json ) browse --connect " $SID " open https://example.com browse --connect " $SID " open https://news.ycombinator.com
3. Stop the tracer, bisect, then pull platform artifacts and release.
node scripts/stop-capture.mjs my-run node scripts/bisect-cdp.mjs my-run node scripts/bb-finalize.mjs my-run --release Attaching to a session that's already running (e.g. one your production worker created) — bb-capture.mjs accepts a session id instead of --new :
Pick a running session (filter client-side; bb sessions list has no --status flag)
bb sessions list | jq -r '.[] | select(.status == "RUNNING") | .id' node scripts/bb-capture.mjs < session-id
mid-flight-debug
...tracer runs alongside the existing automation client; no disruption...
node scripts/stop-capture.mjs mid-flight-debug node scripts/bisect-cdp.mjs mid-flight-debug node scripts/bb-finalize.mjs mid-flight-debug
without --release: leave the session running
What you get from the Browserbase platform
bb-capture.mjs
adds a
browserbase
block to
manifest.json
(session id, project, region, started_at, expires_at, debugger URL).
bb-finalize.mjs
writes:
bb sessions get snapshot (proxyBytes, status, ended_at, …)
logs.json bb sessions logs output (often [])
downloads.zip bb sessions downloads get output (only if the session downloaded files)
When a run was started via
bb-capture.mjs
,
manifest.json
also carries a top-level
browserbase
block:
session_id
,
project_id
,
region
,
started_at
,
expires_at
,
keep_alive
,
debugger_url
.
Summary shape
cdp/summary.json
is the entry point for any analysis: it has session-level totals and a
pages[]
array indexed by top-level
Page.frameNavigated
. Per-page entries are emitted in navigation order (page 0 = first concrete URL).
{
"sessionId"
:
"45f28023-…"
,
"duration"
:
{
"startMs"
:
1777312533000
,
"endMs"
:
1777312609000
,
"totalMs"
:
76000
}
,
"totalEvents"
:
420
,
"pages"
:
[
{
"pageId"
:
0
,
"url"
:
"https://example.com/"
,
"startMs"
:
1777312533000
,
"endMs"
:
1777312538886
,
"durationMs"
:
5886
,
"eventCount"
:
60
,
"domains"
:
{
"Network"
:
{
"count"
:
18
,
"errors"
:
1
}
,
"Console"
:
{
"count"
:
2
}
,
"Page"
:
{
"count"
:
24
}
,
"Runtime"
:
{
"count"
:
13
}
}
,
"network"
:
{
"requests"
:
4
,
"failed"
:
1
,
"byType"
:
{
"Document"
:
2
,
"Script"
:
1
,
"Other"
:
1
}
}
}
]
}
startMs
/
endMs
/
durationMs
are wall-clock ms, derived from
manifest.started_at
plus the offset of each event's CDP monotonic timestamp.
domains[*]
only includes
errors
/
warnings
keys when non-zero.
Drilling in with
query.mjs
For interactive exploration, use
scripts/query.mjs
one-line table of pages
node scripts/query.mjs my-run page 1
full summary for page 1
node scripts/query.mjs my-run page 1 network/failed
cat failed.jsonl for page 1
node scripts/query.mjs my-run errors
all errors across pages, attributed by pid
node scripts/query.mjs my-run errors 2
errors from page 2 only
node scripts/query.mjs my-run hosts
top hosts by request count
node scripts/query.mjs my-run host api.example.com
all requests/responses for a host
node scripts/query.mjs my-run summary
full summary.json
Behind the scenes it just reads
cdp/summary.json
and the
cdp/pages/
All failed network requests (use jq -c to keep it line-delimited)
jq -c '.params' .o11y/ < run
/cdp/network/failed.jsonl
Find requests to a specific host
jq -c 'select(.params.request.url | test("api\.example\.com"))' \ .o11y/ < run
/cdp/network/requests.jsonl
4xx/5xx responses
jq -c 'select(.params.response.status >= 400) | {status: .params.response.status, url: .params.response.url}' \ .o11y/ < run
/cdp/network/responses.jsonl
Console errors only
jq -c 'select(.params.type == "error")' .o11y/ < run
/cdp/console/logs.jsonl
Sequence of URLs visited
jq -r '.params.frame.url' .o11y/ < run
/cdp/page/navigations.jsonl
Find the screenshot taken closest to a timestamp (e.g., when an exception fired)
- ls
- .o11y/
- <
- run
- >
- /screenshots/
- |
- sort
- |
- awk
- -v
- t
- =
- 20260427T1714123NZ
- '
- $0 >= t { print; exit }'
- See
- REFERENCE.md
- for the full jq recipe library and a method-by-method bisect map. See
- EXAMPLES.md
- for end-to-end debug scenarios.
- Best practices
- Use
- bb-capture.mjs
- on Browserbase
-
- it enforces
- --keep-alive
- , fetches the connectUrl, captures the debugger URL, and stamps the manifest. Doing it manually invites mistakes.
- Don't
- --release
- a session you don't own
- :
- bb-finalize.mjs --release
- is for sessions
- you
- created with
- --new
- . When attaching to a production session via
- bb-capture.mjs
- , run
- bb-finalize.mjs
- without
- --release
- so the original automation keeps running.
- Order matters for remote
-
- on Browserbase, attach the main automation client before (or together with) the tracer, and create the session with
- --keep-alive
- . Otherwise the session ends as soon as the tracer's WS closes.
- Don't poll faster than ~1s
-
- each sample opens a one-shot CDP connection and screenshots Chrome. 2s is a good default.
- Pick domains deliberately
-
- defaults (
- Network Console Runtime Log Page
- ) cover most debugging. Add
- DOM
- for DOM-tree mutations (very noisy) via
- O11Y_DOMAINS="$O11Y_DOMAINS DOM"
- .
- Use
- --connect
- for the automation client on remote
- , not a fresh
- browse env remote
- (which would create a new session each time).
- Always run
- stop-capture.mjs
- , even after a crash, so background processes don't linger and the manifest gets
- stopped_at
- .
- Bisect once per run
- :
- bisect-cdp.mjs
- is idempotent — it overwrites the per-bucket files from
- raw.ndjson
- each time.
- Troubleshooting
- browse cdp exited immediately
-
- usually means the target is unreachable (wrong port) or the Browserbase session has already ended. For remote, verify with
- bb sessions get
- — if
- status
- is
- COMPLETED
- , recreate with
- --keep-alive
- and attach automation first.
- Empty
- raw.ndjson
- even though processes are running
-
- confirm a CDP client is actually driving the page. The tracer only emits events that the browser generates, so an idle browser produces ~5 lines of attach/discover messages and nothing else.
- Screenshots all look identical
-
- check
- index.jsonl
- — if
- url
- doesn't change, the page hasn't navigated yet. The polling loop runs independently of the main automation's pace.
- Browserbase session ends mid-run
-
- it likely hit
- --timeout
- . Recreate with a higher timeout (
- BB_SESSION_TIMEOUT=1800 node scripts/bb-capture.mjs --new ...
- ) or remove the timeout flag.
- bb-capture.mjs
- says "not RUNNING"
-
- the session you tried to attach to ended. List candidates with
- bb sessions list | jq '.[] | select(.status == "RUNNING")'
- and try again.
- browserbase/logs.json
- is empty
- []
- expected — bb sessions logs is sparse in practice. The CDP firehose in cdp/raw.ndjson is the source of truth. Where's the session recording (rrweb)? : bb sessions recording is deprecated; this skill doesn't fetch it. Use the screenshot stream in screenshots/ and DOM dumps in dom/ . For full reference, see REFERENCE.md . For example debug runs, see EXAMPLES.md .