Skip to content

WHOOP 4.0 (fw 41.13.2.0): SET_CLOCK and SET_ALARM_TIME not applied – strap logs "GENERIC CMD CRC FAILURE"; flash disabled until clock set via official app #2869

Open

Description

@jubikes

NOOP version

12.0.0

Platform

macOS

OS version

26.5

WHOOP strap / Oura ring model

WHOOP 4.0

Where the data is coming from

Live Bluetooth (WHOOP strap connected)

Test Centre profile

Not a test-mode bug

What happens

  • Strap unused since 2023, deep-discharged, no WHOOP membership

Bonding, live HR/RR, haptics (buzz) and broadcast-HR all work. Commands that carry a payload do not seem to reach the strap intact: right after every SET_CLOCK and SET_ALARM_TIME, the strap logs BLE SPI: GENERIC CMD CRC FAILURE 4, and neither setting is applied.

1. Clock is never set by NOOP → strap refuses to write flash
After the deep discharge the strap RTC was near epoch (~1971). NOOP sends SET_CLOCK on every connect, but the clock stayed invalid, and the strap logged this 100+ times:

→ Set Clock payload=cb27e16a00000000
→ Set Clock payload=cb27e16a0000000000
strap: Flash: RTC timestamp 31553056 is invalid; not saving data to flash.
strap: BLE SPI: GENERIC CMD CRC FAILURE 4
strap: BLE SPI: GENERIC CMD CRC FAILURE 4
strap: BLE SPI: GENERIC CMD CRC FAILURE 4

GET_CLOCK never answered either (Clock: GET_CLOCK unresponsive — derived rough correlation from Data Range …). Every history offload completed with 0 sensor rows (console-only / nothing-banked), and sleep detection reported grav=0 … reason=no-motion.

Workaround that worked: I connected the strap once to the official WHOOP app, which set the clock (Boot RTC: … 10-27-2026 22:51:23), then re-paired with NOOP. After that, "not saving data to flash" was gone and the first offload persisted data:

Backfill: session persisted 555 rows (107 with motion, 107 skin-temp) across 1 night(s).

The first full night was then detected and staged correctly.

2. Firmware alarm is not stored

→ Set Alarm Time payload=010c89e16a00000000
Alarm: armed for Wed 06:33 GMT+1 …
→ Get Alarm Time payload=01
Alarm: strap reports NO alarm currently stored (epoch 0) — the arm did not persist on the strap
strap: BLE SPI: GENERIC CMD CRC FAILURE 4

The alarm did not fire. This looks like the same signature as #34, but here the alarm and the clock fail in the same way.

3. Possibly related: GET_DATA_RANGE decoding
Strap backlog pages behind: not decodable from this frame (#689 — offsets may have moved). The "newest banked record" jumps between 2025, 2027 and 2029 within minutes, e.g.:

Get Data Range raw frame: aa1800ff2802227be101f8323c0256039203000000000101a8823ccc

Full strap log attached.

What you expected instead

SET_CLOCK and SET_ALARM_TIME should be applied on this firmware, so that a strap whose RTC has reset doesn't need the official app to start storing data again.

Steps to reproduce

  1. Let a WHOOP 4.0 (fw 41.13.2.0) fully discharge so its RTC resets.
  2. Pair with NOOP 12.0.0 on macOS and keep it connected.
  3. Observe the strap log: SET_CLOCK is followed by "GENERIC CMD CRC FAILURE",
    the RTC stays invalid, and the strap logs "not saving data to flash".
  4. Arm the firmware alarm: GET_ALARM_TIME reads back epoch 0 and the alarm never fires.

Strap log

[06:43:56] Backfill: auto-continuing (#364/#451) — the trim advanced and the strap is still handing over real records (frontier 1807781942, strap-reported newest 1793166114); re-kicking offload 2/24 without waiting the 15-min floor.[06:43:56] → Send Historical Data payload=00[06:43:56] Backfill: session started — historical offload requestedCommand response: SEND_HISTORICAL_DATA(22) → PENDING(2)[06:43:57] Backfill: reached the end of available history (trim=0xFFFFFFFF) - caught up; the strap handed over its banked history earlier this sync. Nothing more to offload.[06:43:57] → Historical Data Result payload=01ffffffffffffffff[06:43:58] Backfill: session ended — reason=HISTORY_COMPLETEBackfill: refreshing dashboard cache from completed syncre-score: trigger=post-offload newData=yessleep SKIPPED 19 day(s) — need ≥200 hrSamples: hrSamples=0 on 19 day(s): 2026-10-08, 2026-10-09, 2026-10-10, 2026-10-11, 2026-10-12, 2026-10-13, 2026-10-14, 2026-10-15, 2026-10-16, 2026-10-17, 2026-10-18, 2026-10-19, 2026-10-20, 2026-10-21, 2026-10-22, 2026-10-23, 2026-10-24, 2026-10-25, 2026-10-26analyzeRecent dayCache reused=0/2 size=2 days=21 missBy=absent:19,hr:2analyzeRecent cost prep=111ms score=85msanalyzeRecent windows hr[read=33966 served=9733 truncated=0 ownerFlips=0 reuseOff=0] rr[read=21910 served=2435 truncated=0 ownerFlips=0 reuseOff=0]rhr day=2026-10-28 floor=45 nightMean=51 inBedSamples=23778 (floor = WHOOP-style lowest-sustained = NOOP RHR; mean = sleeping-HR-app number)resp day=2026-10-28 rpm=14.1resp day=2026-10-27 rpm=nilsleep day=2026-10-28 totalSleepMin=382 stages=71+121+190=382 eff=0.96 matched=1 source=computedhrv day=2026-10-28 window=whole avgHrv=59.3hrv diag day=2026-10-28 rmssd=60ms sdnn=111ms meanNN=1189ms rr=19199/18992 rejected=1% coverage=0.96 collapsedCov=0.95 dupBeats=4 beatAccurate=0.75 rrIntegrity=plausibleeffort score day=2026-10-28 hr=24234 enough=true hrMax=183.0(provided) rhr=45.0 reserve=138.0 method=edwards trimp=0.0 strain=0.0 z0=403.9 z1=0.0 z2=0.0 z3=0.0 z4=0.0 z5=0.0effort calib day=2026-10-28 hrmax=183 src=override tanaka=185 peak=90 rhr=45 sustained=90 span=4workout detect day=2026-10-28 hr=24234 motion=24234 restHR=45 floor=60 motionOK=373 hrMissing=0 hrTooLow=160 active=213 runs=10 bridged=10 longestRunS=97 meanRunS=21 short=10 noHR=0 lowIntensity=0 kept=0sleep day=2026-10-27 totalSleepMin=nil stages=nil eff=nil matched=0 source=computedhrv day=2026-10-27 window=whole avgHrv=nilsleep-detect day=2026-10-27 NO-NIGHT hr=9733 rr=2435 resp=521 grav=521 skin=521 steps=0 provided=0 window=54h reason=staged-noneeffort score day=2026-10-27 hr=9732 enough=true hrMax=183.0(provided) rhr=60.0 reserve=123.0 method=edwards trimp=0.0 strain=0.0 z0=171.8 z1=0.0 z2=0.0 z3=0.0 z4=0.0 z5=0.0effort calib day=2026-10-27 hrmax=183 src=override tanaka=185 peak=107 rhr=60 sustained=107 span=5workout detect day=2026-10-27 hr=9732 motion=520 restHR=61 floor=76 motionOK=65 hrMissing=0 hrTooLow=64 active=1 runs=1 bridged=1 longestRunS=0 meanRunS=0 short=1 noHR=0 lowIntensity=0 kept=0analyzeRecent stepsMotion reused=59/60 size=60analyzeRecent storeProbes total=6ms dayOwner=81/2ms ownerHr=0/0ms gravityFp=60/4msDedup(#899): swept 2 device id(s), removed 0 overlapping duplicate session(s).analyzeRecent postLoop total=146ms dayReplay=2ms baselines=1ms score2=17ms watchFold=0ms persist=1ms weekly=0ms steps=23ms sleepWrite=95ms sleepHeal=1ms workouts=6ms tail=0msre-score: done — scored 2 night(s) in 565 ms (#1005)

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, or — if I'm on an Oura ring — the Oura ring support table, and this isn't an already-known limit (e.g. SpO₂, recovery/sleep on the 5/MG, or no Recovery / HRV on a ring-only day).
  • I've left out anything personal (strap serial, account email, raw export).

Activity

  1. BSchneppe commented on Nov 9, 2026

    @BSchneppe

    Same failure on Android, with a strap sold new in box and never paired with the official app. I captured the official app's first connect over HCI. After that connect the clock latched, and NOOP's next sync persisted data.

    Setup

    • WHOOP 4.0, firmware harvard 41.16.3.0 / boylston 17.2.2.0 (read from the capture)
    • NOOP 12.0.0-staging (build 554), Pixel 9a, Android 17
    • No WHOOP membership. I installed the official app once, stopped at its payment screen, and uninstalled it.

    Before, with NOOP only

    • The export header said Offload: no history rows ever persisted and motion NO. Every night logged reason=no-motion-no-hypnogram grav=0.
    • NOOP sent SET_CLOCK (8-byte and 9-byte forms) and GET_CLOCK on every connect. The export has no GET_CLOCK(11) reply line.
    • The newest-record timestamps from GET_DATA_RANGE varied between 2025-03, 2025-05 and 2030-01, with Heal #547 purges and clock future-dated skips.
    • Every sync ended with banked nothing … newest stored record is about 543–613 day(s) old. My guess is that these records are factory test data.
    • Sleep detection used the HR-only spine and kept about 3.5 h of a full night. Early-night HR stayed above bandBpm, and a shorter run fell under minSleepMin=60.

    Strap console in the HCI capture (these lines don't appear in the NOOP export):

    Flash: RTC timestamp 31688684 is invalid; not saving data to flash.
    

    31688684 seconds puts the RTC in 1971, as in the original report.

    The official app's first connect, decoded from writes to the command characteristic:

    TX seq=0 GET_HELLO_HARVARD  00                      -> SUCCESS
    TX seq=1 SET_CLOCK          <u32 secs LE> 39040000 00
       frame aa10005723010a2c95f16a3904000000bd1b4689   -> CMD_RSP SET_CLOCK 0101000000 (SUCCESS)
    TX seq=2 cmd 76             00                      -> SUCCESS (reply carries the strap's name)
    TX seq=3 GET_DATA_RANGE     00                      -> newest record dated 2011-01-09
    TX seq=4 SEND_HISTORICAL_DATA 00                    -> PENDING
    TX seq=5 ABORT_HISTORICAL_TRANSMITS 00
    TX seq=6 RUN_HAPTICS_PATTERN 0200000000
    about 30 s later:
    TX seq=7 GET_DATA_RANGE     00                      -> newest record dated at the current wall-clock time
    TX seq=8.. SEND_HISTORICAL_DATA / HISTORICAL_DATA_RESULT acks
    

    The official app's handshake differs from NOOP's in two places:

    1. Payload. The official app's SET_CLOCK is 9 bytes, [secs u32 LE][0x00000439 u32 LE][00], so the second field is 1081. I don't know what that field encodes. NOOP sends [secs][00 00 00 00] and [secs][00 00 00 00 00].
    2. Order. The official app sends GET_HELLO_HARVARD, waits for the reply, and then sends SET_CLOCK. NOOP sends hello, set-clock, get-clock, stop-raw and get-range in one burst, and the export shows ERROR_GATT_WRITE_REQUEST_BUSY retries during it.

    One capture can't show which difference matters.

    After re-pairing with NOOP, with no other changes

    clock: GET_CLOCK(11) reply result=SUCCESS(1)
    Strap newest banked record: <today> (from data range)
    Clock: seeded backfiller correlation from Data Range (… offset 16s)
    Backfill: session persisted 507 rows (97 with motion, 97 skin-temp) across 1 night(s).
    

    NOOP's GET_CLOCK now gets replies, and the offload persists rows.

    Suggestions

    • Send SET_CLOCK in the official app's form (non-zero second field), right after the hello reply and before other commands.
    • When GET_CLOCK never replies and the data range is far in the past or the future, the export could say that the strap clock never latched and flash recording is off. Today it only says the strap "stopped saving history to its flash".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions