Repository navigation
[master] Bug report #1040
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
NOOP version
9.3.0-staging
Platform
Android
OS version
16
WHOOP strap model
WHOOP MG
Where the data is coming from
WHOOP CSV export import
Test Centre profile
Connection & Sync
What happens
My Whoop MG is continuously reconnecting, connected, reconnecting etc.
What you expected instead
A stable fully connected Whoop band
Steps to reproduce
1.close the app, reopen the app
2.disable Bluetooth, enable Bluetooth
3.remove whoop from phone and make new connection
Strap log
<details><summary>Strap log (last 40 lines, redacted)</summary>
11:44:57 sleep day=2026-07-19 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-07-18 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-07-17 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-07-16 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-07-15 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-07-14 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-07-13 SKIPPED hrSamples=0 (need ≥200)
11:44:57 sleep day=2026-08-02 totalSleepMin=nil stages=nil eff=nil matched=0 source=computed
11:44:57 hrv day=2026-08-02 window=whole avgHrv=nil
11:44:57 [universal] dayOwner day=2026-08-02 readId=whoop-D0:••:••:••:••:CF writeActiveId=whoop-D0:••:••:••:••:CF hrRows=2439 provenance=measured
11:44:57 fitnessAge gate day=2026-08-02 rhrNights=0 activityDays=1 computed=false
11:44:57 MTU negotiated: 247 (status=0)
11:44:57 Discovering services (mtu=247)
11:44:57 WHOOP 5/MG detected — will send CLIENT_HELLO after subscribing (experimental).
11:44:57 Subscribed 00002a37-0000-1000-8000-00805f9b34fb
11:44:57 Subscribed 00002a19-0000-1000-8000-00805f9b34fb
11:44:57 WHOOP 5/MG: writing CLIENT_HELLO to fd4b0002 with response (to trigger bonding, experimental).
11:44:58 → DISABLE_ALARM payload=02ff (puffin)
11:44:58 Alarm: disarmed (5/MG rev2)
11:44:58 WHOOP 5/MG: live HR streaming — marking the link established (experimental).
11:45:00 Signal: RSSI -49 dBm
11:45:00 [connection] connect down (uptime ends)
11:45:00 [connection] reconnect n=816 reason=localTerminate
11:45:00 Disconnected (status=22); reconnecting directly in 3s (attempt 1, held 3s)
11:45:06 [connection] connect up gen=852 latencyMs=7847081 uptimeStart=1785663906
11:45:06 Connected — requesting MTU 247 before discovery
11:45:07 MTU negotiated: 247 (status=0)
11:45:07 Discovering services (mtu=247)
11:45:07 WHOOP 5/MG detected — will send CLIENT_HELLO after subscribing (experimental).
11:45:07 Subscribed 00002a37-0000-1000-8000-00805f9b34fb
11:45:07 Subscribed 00002a19-0000-1000-8000-00805f9b34fb
11:45:07 WHOOP 5/MG: writing CLIENT_HELLO to fd4b0002 with response (to trigger bonding, experimental).
11:45:08 → DISABLE_ALARM payload=02ff (puffin)
11:45:08 Alarm: disarmed (5/MG rev2)
11:45:08 WHOOP 5/MG: live HR streaming — marking the link established (experimental).
=== Capture check ===
universal: present (dayOwner day=)
connection: MISSING (expected clockDrift )
INCOMPLETE: missing connection
bundle ok files=2 report=present meta=present screenshot=present crash=present leaks=0
</details>
Before you submit
- I attached my exported NOOP .zip (not a screenshot of the Live screen). If my bug is a clearly reproducible UI issue I described exactly how to see it instead.
- I attached or pasted a strap log above — or my bug is a clearly reproducible UI issue that doesn't need one (e.g. a typo, a button that does nothing) and I've described exactly how to see it.
- I checked the Troubleshooting guide — especially "history syncs but no data" (most often the strap isn't banking history to flash; charge it to 100% and reconnect).
- I checked the FAQ and — if I'm on a WHOOP 5.0 / MG — the 5.0/MG status page, and this isn't an already-known limit (e.g. SpO₂, or recovery/sleep on the 5/MG).
- I've left out anything personal (strap serial, account email, raw export).
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
ryanbr commentedon Aug 2, 2026
Thanks — this is a good capture, and the log is enough to place it even though it can't yet name the cause.
What the log says
The link is being dropped by NOOP, not by the strap:
GATT status 22 is
GATT_CONN_TERMINATE_LOCAL_HOST— our owngatt.disconnect(). The strap isn't going away; something on our side is closing the connection about 3 seconds after it comes up, every time, 816 times. The sequence is identical each cycle: MTU 247 → discover → subscribe → CLIENT_HELLO → DISABLE_ALARM → "live HR streaming — marking the link established" → teardown ~3 s later. So the handshake fully succeeds and then something tears it down immediately after.That is the same class as #1020 (thousands of localTerminate reconnects).
What I need to take it further, and why your log can't give it
There are five different paths in our code that produce this exact status-22 teardown, and your log line is the bare
reason=localTerminate— it doesn't say which one fired. That's not your capture's fault: naming the path landed in #1023 after the 9.3.0 tag. On v9.3.0 the code readsand on current main it reads
So your 9.3.0-staging build physically cannot print the origin.
Could you re-capture on the 9.3.1 testing build? https://github.com/ryanbr/noop/releases/tag/testing-latest —
NOOP-android-v9.3.1.apk. It installs beside your current app under its own id, so you don't lose anything. It carries #1023 (thevia=origin) plus #1027 (offload throughput and interrupted-backfill lines). Same steps — Connection & Sync profile on, let it loop for a minute, export. Thevia=suffix should point straight at the responsible path, most likely the bond watchdog (#50/#971) given the ~3 s hold, but I don't want to guess when one line will say it.Your report was mis-flagged INCOMPLETE — that's our bug, not yours
Your export ends with:
Ignore that. The Connection domain plainly did capture — your log is full of
[connection] connect up / connect down / reconnect n=lines. The completeness check just looks for the wrong marker:clockDriftas proof the Connection mode workedclockDriftis emitted from the strap-clock/history read, which in a reconnect loop never gets a chance to run — so a connection failure guarantees the connection check reads MISSINGbondStatetaggedTestDomain.CONNECTION(WhoopBleClient.kt:7578), which would have proved it, but the check doesn't accept that token.connection: ["clockDrift", "bondState"]So this is an Android-only gap against the Swift map (whose comment claims the two are byte-identical). Worth fixing so the next connection report isn't dismissed as an incomplete capture.
Second thing your log exposes
7,847,081 ms is 2.18 hours, but the previous drop was 6 seconds earlier.
connectAttemptStartedAtMsis assigned only inconnect()and never reset on the auto-reconnect path, so after the first attempt the number is measured from the original connect and just grows. In the one scenario this instrumentation exists to diagnose — a reconnect loop — it reports a meaningless figure that could easily be misread as a stall.Not a separate problem
The
sleep day=… SKIPPED hrSamples=0 (need ≥200)rows are a consequence, not an extra bug: the link never survives long enough to offload history, so there is nothing to score. They should resolve once the loop is fixed.Stop a connection bug from invalidating its own capture, and tag the …
Stop a connection bug from invalidating its own capture, and tag the …
Stop a connection bug from invalidating its own capture, and tag the …
ryanbr commentedon Aug 2, 2026
Correction to my earlier comment: I said the bond watchdog (#50/#971) was the likely culprit "given the ~3 s hold". That is wrong, and it would have sent this down the wrong path.
BOND_WATCHDOG_MS = 7_000L, andbondWatchdogBackoffonly ever WIDENS that window per consecutive bounce. So the bond watchdog cannot fire at 3 seconds — 7 s is its floor. Your log shows the link held for 3 s, every cycle:That rules the watchdog out rather than implicating it. The keep-alive bounce is a 120 s liveness check, so that is out too. What remains among our own teardown routes is a GATT operation throwing (
gattThrow:<op>), or the multi-WHOOP re-adopt — and re-adopt is precisely the one route that was never tagged, so it would printvia=unknownrather than naming itself.So the re-capture matters more than I implied, and I should not have offered a guess ahead of it.
One practical note on which build to use. The 9.3.1 testing build tags four of the five routes. If your capture comes back
via=unknown, that is not a dead end — with four routes named,unknownpoints at the untagged re-adopt path by elimination. #1046 tags that fifth route and also stops the Connection capture flagging itself INCOMPLETE, so a build cut after it merges would name all five directly. Either build is worth capturing on; the newer one just answers it in one step instead of two.To be explicit about scope: #1046 does not change any connection behaviour. It will not stop your strap reconnecting. It exists so the next capture identifies which code path is dropping the link, which is what a real fix has to be built on.
Stop a connection bug from invalidating its own capture, and tag the …
nielsdelange commentedon Aug 2, 2026
I did a full hard reset on the Whoop band, did the pairing again and for now it is stable, I will keep you posted!
ryanbr commentedon Aug 3, 2026
Would test with the latest build, if it happens again we'll have additional debug info
Stop a connection bug from invalidating its own capture, and tag the …