Skip to content

FUG-93: per-reservation BLE HCI (btmon) capture for reservation agents - #70

Open
issuefleet[bot] wants to merge 2 commits into
mainfrom
agent/fug-93-hitl-expose-btmon-hci-capture-to
Open

FUG-93: per-reservation BLE HCI (btmon) capture for reservation agents#70
issuefleet[bot] wants to merge 2 commits into
mainfrom
agent/fug-93-hitl-expose-btmon-hci-capture-to

Conversation

@issuefleet

@issuefleet issuefleet Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What & why

An agent debugging BLE inside a reservation had no way to see link-level truth: it could observe that BleakClient.connect() timed out, but not why — whether CONNECT_IND went unanswered, the peripheral answered and the link dropped, or the central never issued the request. Those have different fixes and the harness logs can't tell them apart. This is the blocker on settling the FUG-61 provisioning-flake root cause.

This adds host-side btmon HCI capture an agent holding a reservation can drive and read back as a btsnoop, with no host access and no rig redeploy — approach (1) from the ticket, refined to per-reservation scoping.

Design

btmon needs an AF_BLUETOOTH HCI monitor socket (HCI_CHANNEL_MONITOR) + CAP_NET_RAW/CAP_NET_ADMIN, which the unprivileged reservation container deliberately lacks (containers are handed to agents). So capture runs host-side — the daemon is already root — as btmon -w per reservation into that reservation's state dir, bind-mounted read-only into the container at /run/hitl/capture. start/stop go through the daemon API (POST /reservation/{id}/btmon/{start,stop}, GET …/btmon); the btsnoop is read back over the existing SSH path. No capability changes anywhere.

CLI: hitl btmon start | stop | status | fetch <out.btsnoop> [--mac] | capture [--mac] --out F -- <cmd> (the last is the windowed start→run→stop→fetch convenience).

Acceptance criteria

  • Capture + retrieve own sessionhitl btmon start --id RESfetch. ✓
  • Opens in btmon -r and Wireshark — native btsnoop; stop sends SIGTERM (not SIGKILL) so the file flushes cleanly. ✓
  • Annotated by DUT BLE MACfetch --mac prints the bluetooth.addr == <mac> Wireshark filter; the shared-adapter caveat is stated plainly. ✓
  • Multiple DUTs concurrently — each reservation gets its own btmon on the shared HCI monitor channel (a broadcast channel; multiple readers coexist). ✓
  • Bounded — a watchdog stops each capture at a size cap and a max-duration, so it can never fill the rig disk; off by default (nothing runs until start). ✓
  • Torn down on release / lease expiry — the btmon process is killed and the file removed with the state dir in runner.Stop. ✓
  • Documentedpi/hitl/README.md, pi/hitl/DESIGN.md (with the caveat and the updated per-DUT-BLE-radio open item), and a line in DEVELOPERS.md. ✓

Shared-adapter caveat (documented, not hidden)

The rig has one Bluetooth controller shared by every DUT (DESIGN.md already lists "per-DUT BLE radio" as an open item). A trace therefore contains other DUTs' BLE traffic for its window. This is stated plainly; the trace is annotated by the DUT BLE MAC, not isolated. The real fix — a per-DUT USB BT dongle passed through FUG-73-style — is recorded as the eventual direction, not done here.

Verification

  • Offline: bazel test //pi/hitl/... — runner/queue/daemon Go units (incl. new capture tests: disabled-guard, status shape, start/stop lifecycle with a stand-in btmon, byte formatting) all pass. gofmt clean; nix files parse; all default bazel targets build (hermetic Go SDK).
  • Pending — on hardware: confirm a real capture opens in btmon -r/Wireshark and is torn down on release, batched into one reservation. This worktree has no network creds to reach the rig; needs a relayed run.

Files

pi/hitl/internal/runner/{capture.go,podman.go,runner.go}, internal/queue/queue.go, internal/api/api.go, cmd/hitl-managerd/main.go, cmd/hitl/main.go, nix/{hitl-app,container}.nix, plus README.md / DESIGN.md / DEVELOPERS.md and Go unit tests.

Closes-Linear: FUG-93 (https://linear.app/fughilli/issue/FUG-93/hitl-expose-btmon-hci-capture-to-agents-inside-the-reservation)

An agent debugging BLE inside a reservation had no way to see link-level
truth — only that BleakClient.connect() timed out, not whether CONNECT_IND
went unanswered, the peripheral answered and the link dropped, or the central
never issued the request. This adds host-side btmon capture an agent can drive
and read back as a btsnoop.

btmon needs an AF_BLUETOOTH HCI monitor socket + CAP_NET_RAW/ADMIN the
unprivileged reservation container deliberately lacks, so capture runs
host-side (the daemon is already root): `btmon -w` per reservation into the
reservation's state dir, bind-mounted read-only into the container at
/run/hitl/capture. start/stop go through the daemon API; the btsnoop is read
back over the existing SSH path. No capability changes anywhere.

- Runner: StartCapture/StopCapture/CaptureStatus keyed on reservation id, a
  watchdog bounding each capture by a size cap and a max-duration auto-stop
  (can't fill the rig disk), SIGTERM on stop so the btsnoop flushes cleanly,
  and teardown wired into Stop/Cleanup so a capture dies with its reservation.
- Queue/API/daemon: capture gated on an active reservation; three routes
  (POST .../btmon/{start,stop}, GET .../btmon) + a --btmon* flag set.
- CLI: `hitl btmon start|stop|status|fetch|capture` (the last is the windowed
  start→run→stop→fetch form). fetch/capture annotate the trace with the DUT
  BLE MAC and print the Wireshark filter.
- nix: bluez on the daemon (btmon path) + rig; container note for `btmon -r`.

Shared-adapter caveat, documented not hidden: the rig has ONE Bluetooth
controller shared by every DUT (DESIGN.md open item), so a trace contains
other DUTs' BLE for its window. It's annotated by the DUT BLE MAC, not
isolated; docs point at the per-DUT-dongle follow-up as the real fix.

Verified offline: go build + unit tests for runner/queue/daemon via bazel
(hermetic Go SDK), nix files parse, all default targets build. The on-hardware
run (opens in btmon -r/Wireshark, teardown on release) remains for the rig.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://fughilli.github.io/splanc/pr-preview/pr-70/

Built to branch gh-pages at 2026-08-10 21:24 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

CI `prek` (prettier) flagged `*every*`; prettier normalizes emphasis to
underscores (`_every_`), matching the rest of the file. Reproduced offline with
prettier + markdownlint + nixpkgs-fmt + buildifier over the changed files; all
clean now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants