feat(network): capture in-app HTTP traffic for React Native sessions
Summary
network dump on a React Native session with a Metro dev-server binding now installs a JS fetch/XMLHttpRequest interceptor into the app runtime over the Metro inspector (CDP Runtime.evaluate) and reports real traffic: method, full URL, status, duration, headers, and bounded bodies. Previously it only parsed app-log lines, which on RN name almost nothing.
The first dump arms the hook and answers that window from the app log (source=app-log plus an arming note); later dumps answer from the in-app ring buffer (source=provider, drain-once, newest-first, 200 entries). No Metro binding, an unreachable dev server, several connected apps, or a session device the host cannot name uniquely keeps the old log path with a typed note. The provider injects only into the runtime whose reported device equals the platform-composed Metro device identity — a name that marks exactly one device this host can see (Android reads every adb devices serial in any state and withholds the name when any read fails; Apple checks booted simulators in every simulator set the host can name plus CoreDevice's connected devices, and ADR 0038 records the one residue it cannot see: a set directory the host was never told about); anything weaker fails closed with metro-device-mismatch before any injection. The identity probe resolves lazily inside a dump, so only a session whose transport declared a provider dump pays for it. A session without an app never reaches the provider at all. Bodies/headers are capped and redacted with the shared kernel rules. Provider preference lives inside the Apple/Android runtimes via the existing NetworkTransport slot, so network dump stays always-available. docs/adr/0038 records the source owner, the probe | provider | app-log enum, and the lazy-arming opt-in policy shared with #3928.
agent-device network dump # arms the capture (answers from app log)
agent-device network dump --include all # method, URL, status, duration, headers, bodies
Rejected alternatives
- RN DevTools' built-in Network CDP domain instead of injected code: no app-side injection, but it needs a daemon-held CDP connection for the session's lifetime and drops RN versions without the domain. The injected runtime (~320 lines) is the version-agnostic core; which floor we commit to belongs in the source-selection ADR alongside #3928.
- Arming at app open when Metro is bound: rejected for now. Injection stays coupled to the first explicit
network dump, so nothing is installed in the app under test until a command asks for network data. Open-path arming needs an attach-retry policy for cold-start races and pre-empts the #3928/ADR injection-policy decision.
Validation
pnpm check:affected --runat head3271f8738e: all runnable checks passed (exactCommit=true, basec48d27a819, receipt.tmp/validation/run-DWkJRo/receipt.json);fallow audit --base c48d27a819passes on changed files.- Review round 3 (fail-closed identity): Apple checks every simulator set the host can name (own set, default set, operator set) plus CoreDevice-connected devices and fails closed on any unreadable answer; Android reads every listed serial in any state with bounded timeouts and withholds the identity on any unreadable serial; the probe resolves lazily inside the dump. Live iOS at the final head: arming dump,
source=providercapture, drain-once, then a second booted same-named simulator flips the dump tosource=app-logwith the mismatch note, and shutting the twin restores it. Verbatim outputs in the round-3 reply. - Review round 2 (device identity):
metroDeviceIdentity(device)is platform-owned — Android composes<model> - <release> - API <level>only when unique acrossadb devices, Apple passes the simulator name only when unique among booted simulators; anything weaker fails closed withmetro-device-mismatchbefore any injection. Live on two same-image emulators (bothsdk_gphone64_arm64): shared model fails closed with no injection on either device; one attached device arms and captures the full sequence. Verbatim outputs in the round-2 reply. - Live iOS: simulator
rn-net2 ios, final head — arming dump (source=app-log+ arming note), thensource=providerdumps with full URL/status/durationMs/headers/bodies for the network lab's fetch/POST/XHR (httpbin),x-api-keyshown as[REDACTED]; drain-once verified; cold restart re-arms; a failed fetch followed by an XHR to the same URL reports both entries. - Live Android: emulator
ReactNative_API_35— same sequence; entries carry theokhttp/4.12.0User-Agent. - Live two-device Metro (one Metro, iOS sim + Android emulator, two sessions): each session's dump returned only its own runtime's entries; draining one left the other at
matchedLines=0. - Full CLI outputs and per-finding disposition in the review-round reply.
🤖 Generated with Claude Code