Browsers, QUIC, and what watson can and cannot see
watson watches native apps passively, from the wire. Browsers add two problems. Every web app's traffic comes from one process, and most browsers speak HTTP/3 (QUIC), which passive capture cannot see into. This page explains how watson handles both and what you see in each case.
The short version
- A browser extension (the browser observer) runs in any Chromium-based browser: Chrome, Edge, Brave, Arc, Opera, Vivaldi. For each streamed answer it reports when each chunk arrived and how big it was, never the content. That gives watson the exact first byte, duration and pauses of a web chat.
- Passive capture adds the retransmits and round-trip times the page cannot see, and those tell a network stall from a provider wait.
- Passive capture can only name a browser connection whose TLS handshake it watched. Over QUIC, or on a connection that was already open when
watchstarted, it cannot. You then get the extension's exact timing, no path health, and a score from the first byte alone. - On macOS, watson also samples the first hop (Wi-Fi signal and pings to the router) and the machine itself (CPU, memory, heat). Neither depends on reading the stream, so they still explain some pauses under QUIC. See what is still visible under QUIC.
What you see in each situation
| Situation | Passive capture | Row | Network evidence | Score |
|---|---|---|---|---|
HTTP/2 (TCP), watch started before the connection opened | sees the handshake, names the connection | browser | round-trip, retransmits, stalls | full: health, TTFB, stalls. High once baselines fill |
HTTP/2, connection already open when watch started | blind (no handshake) | browser-only, flag h2 | first hop only (macOS) | TTFB only, shown 100~. Medium |
| HTTP/3 (QUIC) | blind (see below) | browser-only, flag h3 | first hop only (macOS) | TTFB only, shown 100~. Medium |
| No extension installed | names TCP connections it saw, as multiplexed pieces; nothing for QUIC | passive pieces | yes, for TCP | normal, Low (multiplexed) |
Extension running, watch not running | n/a | nothing: serve stores the report, but watch skips reports from before it started |
The reason on a browser-only row says which case it is:
HTTP/3 (QUIC) hides retransmits and round trips from passive capture, so the score is TTFB onlythe page used h2 but passive capture saw no matching connection (it may have been open before watch started), so the score is TTFB onlypassive capture saw only QUIC for this stream although the page used h2, so the score is TTFB onlyno passive evidence: ...when the page could not say which protocol it used (the browser may not provide it for every host)
A TTFB-only score is drawn as 100~ ttfb in the terminal (dimmed) and as a dashed pill on the page, so a weaker score looks weaker.
How a browser stream becomes one record
Passive capture often splits one long stream with pauses into several multiplexed pieces on one connection, so the extension's report cannot be matched one-to-one with a passive interaction. Instead:
- The extension sends the report when the stream ends: host, start time, headers time, chunk times and sizes, and the protocol.
watchholds it for the quiet period plus a second (passive capture announces an interaction only after 2 s of quiet), then until the overlapping passive pieces have settled (at most 90 s).- It builds the record from the report (exact first byte, last byte, pauses) and attaches the retransmit and round-trip evidence of the pieces that overlap it, from browser processes only. The first-hop and machine samples for the same window are attached too.
- The covered pieces are listed in the record (
covers) and hidden in bothwatchandserve, so only the merged record shows.
If no piece overlaps, the record is browser-only.
Why QUIC is invisible
QUIC runs over UDP and encrypts its transport headers, so there are no sequence numbers, ACKs or retransmissions on the wire to read. For a QUIC flow watson sees datagram sizes and times and nothing about loss or round trips. For the same reason, native Claude Desktop over QUIC gets only a TTFB-based score.
A QUIC flow also has no readable ClientHello, so only its destination address says what it is. Addresses are shared: Google's front ends serve every Google API, and an idle browser keepalive was once reported as "Google Vertex". watson therefore accepts an address-only match only from a known client app. A browser is not one, so its QUIC flows are ignored.
Relaxing that rule would gain nothing. Naming the flow would give watson datagram timing, which the page already has more precisely, and QUIC would still hide the retransmits and round trips that matter. The extension's report is already the best source for a QUIC stream. What is missing is path health, and no passive change can add it.
What is still visible under QUIC
These causes come from samplers that never look at the stream, so they work the same for a QUIC or browser-only record (all macOS only for now):
- A router ping that spiked or was lost during the pause names it local link (
L). Spikes sent while the machine's CPU was saturated are ignored, because a busy machine delays its own echoes. - Memory pressure or swapping names it app busy (
A). Heat or Low Power Mode while the CPU was saturated names it throttled (H), and saturated CPU alone names it machine contention (C). The browser's own CPU is not checked, because an extension report does not say which process it came from.
Weak Wi-Fi alone does not name a QUIC pause, and nothing can name it upstream: telling "past the router" apart needs the path round trip that QUIC hides. A pause with none of the above stays ? (unattributed). The score stays TTFB only either way.
What QUIC costs you
| Kept | Lost |
|---|---|
| exact first byte (headers), duration, chunk cadence | round-trip time to the server |
| where the pauses are and how long | retransmits |
| rCV, pattern, throughput | telling an upstream stall from a provider wait (those pauses show ?) |
| a score, against the app's own baseline | any score component except TTFB |
| first-hop and machine causes (macOS) |
For a model that takes a while before it writes, the TTFB-only score is nearly constant. The headers arrive in 40 to 120 ms, and the wait that varies is a provider wait, which never lowers the score.
The "started before the browser" limit
watson cannot name a connection it did not see open (no handshake), even over TCP. Chromium browsers reuse HTTP/2 connections for a long time, so starting watch after the browser has been in use gives browser-only rows until it reconnects.
To get a full row:
- Start
watchfirst. - Quit the browser completely and reopen it. Check that it is gone: for Chrome,
pgrep -f "Google Chrome"prints nothing. Chromium browsers can stay alive in the background and keep their connections. - Use the web app.
How to check which protocol the browser is using
- The
h2/h3flag on the row (the protocol the page reports). - The browser's UDP/443 sockets, which show QUIC.
lsofprints Chrome's helper as justGoogle, sogrep chromefinds nothing. Runlsof -nP -iUDP | grep ":443": any lines underGooglemean QUIC is in use. - To turn QUIC off for testing in Chrome:
chrome://flags/#enable-quic, then relaunch.