[BE-0283] feat(android): add network-capture assertions to the adb backend#1180
Merged
Conversation
0x0c
force-pushed
the
claude/be-0283-implementation-11c2e8
branch
from
July 17, 2026 15:06
be0fdab to
7f81921
Compare
0x0c
added a commit
that referenced
this pull request
Jul 17, 2026
targetSdk 35 blocks plaintext HTTP by default, but the network e2e (network_android.yaml) needs cleartext to two loopback aliases with no certificate to serve: 10.0.2.2 (SHOWCASE_API_URL, the emulator's host-loopback alias to net_stub.py) and 127.0.0.1 (BAJUTSU_COLLECTOR, the on-device end of the adb-reverse tunnel BajutsuNet reports exchanges over). Without this, Net.get()'s OkHttp call fails cleartext validation immediately, status never reaches "done", and the scenario's wait times out — the network (adb) CI failure on PR #1180. A scoped network security config permits cleartext for just those two aliases, leaving every other host on the HTTPS-only platform default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0x0c
added a commit
that referenced
this pull request
Jul 17, 2026
Four non-blocking Claude Code review comments on PR #1180: - BajutsuNet.kt: log a transport failure on the fire-and-forget collector POST instead of swallowing it silently, so a "request not observed" assertion failure has a trace to point at. - BajutsuNet.kt: bound the request body copy by contentLength, matching the response body's BODY_PEEK_LIMIT, so a large/unknown-length body is skipped rather than buffered whole. - protocols.py: add the new bridge_collector method to the RunEnvironment surface enumeration. - Makefile: pin E2E_NET_STUB_PORT (was override-safe-looking `?=`) since it must match the hardcoded SHOWCASE_API_URL in network_android.yaml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0x0c
added a commit
that referenced
this pull request
Jul 17, 2026
Poll the stub's port with a bounded curl retry loop (matching the demos/web/Makefile e2e/record targets) instead of assuming the app launch + wait: stable.refresh step always outlasts the ThreadingHTTPServer bind. A fixed sleep would violate determinism-first; this is a condition wait with a clear failure message if the stub never comes up. Addresses PR #1180 review comment 3604402880. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0x0c
marked this pull request as ready for review
July 18, 2026 04:16
0x0c
added a commit
that referenced
this pull request
Jul 18, 2026
targetSdk 35 blocks plaintext HTTP by default, but the network e2e (network_android.yaml) needs cleartext to two loopback aliases with no certificate to serve: 10.0.2.2 (SHOWCASE_API_URL, the emulator's host-loopback alias to net_stub.py) and 127.0.0.1 (BAJUTSU_COLLECTOR, the on-device end of the adb-reverse tunnel BajutsuNet reports exchanges over). Without this, Net.get()'s OkHttp call fails cleartext validation immediately, status never reaches "done", and the scenario's wait times out — the network (adb) CI failure on PR #1180. A scoped network security config permits cleartext for just those two aliases, leaving every other host on the HTTPS-only platform default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0x0c
added a commit
that referenced
this pull request
Jul 18, 2026
Four non-blocking Claude Code review comments on PR #1180: - BajutsuNet.kt: log a transport failure on the fire-and-forget collector POST instead of swallowing it silently, so a "request not observed" assertion failure has a trace to point at. - BajutsuNet.kt: bound the request body copy by contentLength, matching the response body's BODY_PEEK_LIMIT, so a large/unknown-length body is skipped rather than buffered whole. - protocols.py: add the new bridge_collector method to the RunEnvironment surface enumeration. - Makefile: pin E2E_NET_STUB_PORT (was override-safe-looking `?=`) since it must match the hardcoded SHOWCASE_API_URL in network_android.yaml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0x0c
added a commit
that referenced
this pull request
Jul 18, 2026
Poll the stub's port with a bounded curl retry loop (matching the demos/web/Makefile e2e/record targets) instead of assuming the app launch + wait: stable.refresh step always outlasts the ThreadingHTTPServer bind. A fixed sleep would violate determinism-first; this is a condition wait with a clear failure message if the stub never comes up. Addresses PR #1180 review comment 3604402880. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0x0c
force-pushed
the
claude/be-0283-implementation-11c2e8
branch
from
July 18, 2026 04:24
b0f9af9 to
0723099
Compare
added 2 commits
July 21, 2026 08:08
…entation-11c2e8 # Conflicts: # tests/runner/test_pool.py
The roadmap-index merge driver silently dropped the "Device-cloud execution" and "Authoring experience" sections when merging origin/main into this branch, leaving the GENERATED marker pairs absent and make roadmap-index unable to regenerate into them. Restore the scaffolding from origin/main and regenerate, which also moves BE-0283 out of Proposals now that it is Implemented.
Rename the scenario tag from `net` to `network` so it matches demos/web/scenarios/network.yaml, keeping `--tags network` filtering consistent across backends. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WRkGhvrpKGzPk5wu4SsE2
- android-e2e.yml: add the `network` job to the `E2E (android)` required aggregator's needs/results. Its pass/fail is a deterministic, host-independent fact (was the exchange observed?), the same shape as smoke/conformance, so a regression must fail the required check; it is path-gated identically, so a skip still passes. golden/visual stay excluded (host-specific baselines). - android.py: update AndroidEnvironment's class docstring, stale since BE-0283 — Android now has app-side network capture (the app reports to the host collector bridged via `adb reverse`), while the adb driver still declares no native NETWORK capability. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WRkGhvrpKGzPk5wu4SsE2
… need
BAJUTSU_COLLECTOR is a plain-HTTP 127.0.0.1 URL, and Android (API 28+)
blocks cleartext by default (iOS exempts loopback via ATS, so BajutsuKit
needs no equivalent note). Without a network_security_config cleartext
exception for 127.0.0.1, BajutsuNet's report POST fails with
"CLEARTEXT communication to 127.0.0.1 not permitted" — logged but
otherwise silent, so no exchange reaches the collector. The showcase
already carries this exception; document the same requirement for
third-party integrators in BajutsuAndroid/README{,.ja}.md and
docs/network.md (+ ja) "App contract".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WRkGhvrpKGzPk5wu4SsE2
House doc style asks for an acronym's full name in parentheses on first
use. Expand ATS -> App Transport Security (ATS) in the cleartext note
across BajutsuAndroid/README{,.ja}.md and docs/network.md (+ ja).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WRkGhvrpKGzPk5wu4SsE2
…ocs wording - test_adb_lifecycle.py: add two tests mirroring start()'s failure tests — a failing `adb reverse` at setup surfaces a clean DeviceError (CalledProcessError path), and a missing adb binary does too (OSError path). The bridge_collector error-translation branches were untested. - docs/network.md (+ ja): the cleartext-exception note said the report POST "fails silently"; it is actually logged (Log.w), just invisible to the assertion/network.json trail — align the wording with the READMEs so a reader debugging a missing exchange knows to check logcat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WRkGhvrpKGzPk5wu4SsE2
…omically - BajutsuNet.post: onResponse now logs a non-2xx collector reply (a 401 on a missing/mismatched token, a 5xx) instead of dropping it, matching the onFailure transport-failure log — so a rejected report leaves a trace rather than surfacing only as "exchange not observed". - Bundle the collector URL and token into one @volatile CollectorConfig set with a single assignment. Two independent @volatile fields could be read mid-write by a request racing configure(), sending a report with no Authorization header that the collector then 401s silently; one snapshot closes that window. report/reportFailure/post all read the one config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017WRkGhvrpKGzPk5wu4SsE2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports
request/requestSequencenetwork-capture assertions to the Android (adb) backend, closing agap since BE-0007: the runner already pre-starts a
NetworkCollectorper Android device and injectsBAJUTSU_COLLECTORinto the launch env, but nothing on Android ever read it, so arequestassertionsilently degraded to "zero exchanges observed" instead of failing loudly. This merges BE-0283 to
Implemented.BajutsuAndroidgains an OkHttp interceptor (BajutsuNet.kt) that reports each exchange to the samein-process collector iOS uses, and the runner bridges the emulator's isolated loopback to that collector
with
adb reverse— the reverse-direction twin of the resident UI Automator server'sforward_cmd(BE-0245). The scenario schema, assertion pipeline, and capability model are all unchanged:
networkstays a deliberately ungated construct the app-side collector satisfies without a backend advertising it,
exactly the accommodation idb already relies on.
What changed
BajutsuAndroid/src/main/java/dev/bajutsu/android/BajutsuNet.kt(new) — the OkHttp interceptor: ano-op unless
BAJUTSU_COLLECTORis configured, POSTs each completed exchange as JSON matchingNetworkExchangefield-for-field over a separate reporting client. A report failure (a body-peek orJSON-build throw) is caught and logged rather than allowed to fail the app's real request.
bajutsu/adb.py—reverse_cmd/reverse_remove_cmd, siblings offorward_cmd/forward_remove_cmd, tunneling the same device and host port so the injected collector URL resolveson-device unchanged.
bajutsu/platform_lifecycle/protocols.py+environments/{android,ios,web}.py— a newRunEnvironment.bridge_collector(port)seam (a third "no-op implementation" decline idiom, documentedalongside the existing two):
AndroidEnvironmentrunsadb reverseand returns a real teardown thunk;iOS/web return a no-op, since the Simulator shares the host loopback and web observes via the driver.
bajutsu/runner/pool.py— wiresbridge_collectorinto the lease right before launch (alongsidethe existing
BAJUTSU_COLLECTORenv injection) and tears it down on bothrelease()and a launchfailure, so one failed lease never leaks a tunnel.
bajutsu/drivers/adb.py— a comment recording whyAdbDriver.CAPABILITIESmust not advertisenetwork(that token means native driver observation), locked by a preflight test.compose/viewsNet.ktmigrate fromHttpURLConnectionto OkHttpwith
BajutsuNet.interceptor()installed (debug-gatedconfigure()call inMainActivity, matchingthe clipboard receiver's precedent); a new
network_android.yamlscenario and anetwork (adb)job inandroid-e2e.ymlprove arequestassertion observes real emulator traffic end to end, via athrowaway host stub (
net_stub.py) the emulator reaches at its10.0.2.2host-loopback alias.docs/network.md(+ ja),demos/showcase/SPEC.md(+ ja), anddocs/architecture.md(+ ja) updated to describe Android's network capture;
BajutsuAndroid/README.md(+ ja) gains anetwork-capture section.
Scope
Capture is bounded to OkHttp-originated HTTP(S) traffic — the same bounded shape as iOS's
URLSession-only scope.HttpURLConnection, other HTTP clients, andWebViewtraffic are out of scope(a
WebViewneeds its own follow-up, as it did on iOS, BE-0037). Deterministic mocks (stubresponses) are deliberately deferred to a follow-up item — this PR ships observation only, matching the
proposal's "Alternatives considered."
Prime-directive compliance
requestassertion are all deterministic.
sleep; the on-device scenario waits on the mirrored*.statusvalue and the collector via condition waits.
Verification
New fast-gate coverage:
reverse_cmd/reverse_remove_cmdcommand shapes,AndroidEnvironment.bridge_collectorestablishing/tearing down the tunnel (including a teardown-failure-is-swallowed-and-logged case), the
pool wiring the bridge before launch and releasing it on both success and launch failure, the interceptor's
JSON wire-shape parsing into
NetworkExchange, and thatAdbDriver.CAPABILITIESexcludesnetworkwhilea
requestscenario still preflight-cleans.Not covered by
make check(no JVM in the fast gate): the real Kotlin interceptor. It's proven bythe new
network (adb)job inandroid-e2e.yml, an on-device CI run against a real emulator — not yetexecuted in this session since it requires a booted AVD.
Reviewed with the built-in
simplify/code-reviewdiscipline plus thepr-review-toolkitsilent-failure-hunter,type-design-analyzer, andpr-test-analyzeragents; their findings (anunguarded
report()call that could fail the app's real request, a swallowed-without-logging adbfailure, the Protocol docstring's declining-idiom list missing the new method, and an ungated
configure()call in the showcase) are all fixed in this diff.
🤖 Generated with Claude Code