Skip to content

fix(apple-watch): Apple Health shows inflated distance for treadmill workouts (km written as miles) - #4798

Merged
cagnulein merged 2 commits into
masterfrom
claude/qz-apple-health-distance-334010
Aug 11, 2026
Merged

fix(apple-watch): Apple Health shows inflated distance for treadmill workouts (km written as miles)#4798
cagnulein merged 2 commits into
masterfrom
claude/qz-apple-health-distance-334010

Conversation

@cagnulein

Copy link
Copy Markdown
Owner

Root cause

When a workout ends, the Apple Watch writes the total distance to HealthKit via WatchWorkoutTracking.stopWorkOut(). The value it writes comes from WorkoutTracking.distance, which is populated from WatchKitConnection.distance (Watch side) on every heart-rate tick. WatchKitConnection.distance (Watch side) is set from the WatchConnectivity reply that the iPhone sends back whenever the Watch pings it.

The bug is a unit mismatch in that reply value. Depending on which device is the QZ primary:

Scenario How WatchKitConnection.distance (iPhone) is set Unit sent to Watch
iPhone is primary (BLE to treadmill) lockscreen::setDistance(km)km * 0.621371 → stored miles
Another QZ device is primary (e.g. iPad, SENDER=PAD) Connection.swift reads raw ODO= from local-network message km

The Watch always wrote the received value with HKUnit.mile(), so in the iPad-primary scenario it was writing km as if they were miles. A 4.47 km run showed up as 4.47 mi in Apple Health instead of 2.78 mi.

Evidence from the debug log

The log for the reported session (iFIT_Xenon1, 30 min run) shows two interleaved local-network message streams:

SENDER=PHONE  ODO=0.000621371   (first sample: 1 m in miles, correctly converted by lockscreen)
...
SENDER=PAD    ODO=0.000173898   (Watch/iPad km stream starts ~9 s later)
SENDER=PAD    ODO=0.000620490
...
SENDER=PAD    ODO=4.478690      ← 4.47 km = 2.78 mi
SENDER=PHONE  ODO=4.478690      ← PHONE ODO overwritten to km after Connection.swift clobbers WatchKitConnection.distance

At workout end the Watch received 4.47 via WatchConnectivity and wrote HKQuantity(unit: .mile(), doubleValue: 4.47) → Apple Health displayed 4.4 mi instead of 2.78 mi.

Why SENDER=PAD must be kept

Connection.swift reading ODO from SENDER=PAD is intentional and correct: it is how a secondary device (phone/Watch) receives live distance from a primary QZ instance (iPad or another phone). Removing that line would break multi-device setups.

Fix

Standardise the iPhone→Watch WatchConnectivity channel to always carry km. The Watch does the single km→miles conversion at write time.

src/ios/lockscreen.mm — stop pre-converting in the iPhone; pass raw km:

// before
[h setDistanceWithDistance:distance * 0.621371];
// after
[h setDistanceWithDistance:distance];

watchkit Extension/WatchWorkoutTracking.swift — convert at the HealthKit write site:

// before
let miles = WorkoutTracking.distance
// after
let miles = WorkoutTracking.distance * 0.621371

With both changes every distance source (iPhone primary via lockscreen, iPad/multi-device primary via Connection.swift) arrives at the Watch in km, and the conversion to miles happens once, in the right place.

Test plan

  • Solo iPhone + treadmill (iPhone primary): verify Apple Health distance matches QZ display
  • iPad primary + iPhone companion + Apple Watch: verify Apple Health distance matches QZ display on iPad
  • Cycling workout: verify distance is correct (same code path, distanceCycling quantity type)
  • Walking/elliptical/rowing: verify distance is correct

🤖 Generated with Claude Code

…hKit

Il canale iPhone→Watch via WatchConnectivity trasportava un valore con
unità inconsistente: miglia quando l'iPhone era il device primario
(lockscreen::setDistance convertiva km→mi), km quando un altro device
QZ (iPad, "SENDER=PAD") inviava la distanza grezza. Il Watch scriveva
sempre il valore ricevuto come HKUnit.mile(), causando distanze gonfiate
in Apple Health (4.4 mi invece di 2.78 mi per 4.47 km).

Fix: standardizzare il contratto a "sempre km" su quel canale.

- lockscreen.mm: rimuove la conversione * 0.621371, invia km grezzi
- WatchWorkoutTracking stopWorkOut(): moltiplica per 0.621371 prima di
  scrivere su HealthKit, così il Watch fa sempre la conversione finale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cagnulein

Copy link
Copy Markdown
Owner Author

mail from
Uwe W.

@stale

stale Bot commented Jul 28, 2026

Copy link
Copy Markdown

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the wontfix This will not be worked on label Jul 28, 2026
@stale stale Bot closed this Aug 7, 2026
@cagnulein cagnulein reopened this Aug 8, 2026
@stale stale Bot removed the wontfix This will not be worked on label Aug 8, 2026
@cagnulein
cagnulein merged commit 381841f into master Aug 11, 2026
51 of 58 checks passed
@cagnulein
cagnulein deleted the claude/qz-apple-health-distance-334010 branch August 11, 2026 11:50
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.

1 participant