A 15-minute run on 7 July exists three times in our database. Garmin holds it as running, 174 kcal, 930 seconds. Apple Health holds it as Running, 174 kcal, 935 seconds. Strava holds it as Run, 215 kcal, 930 seconds. One athlete, one pair of shoes, one morning, and three rows that a naive weekly total counts as three runs and 563 kcal.
None of the three did anything wrong. Garmin recorded the run. Garmin Connect shared it with Apple Health, as the athlete had asked it to, and with Strava, likewise. Strava then wrote its own copy into Apple Health, because that is what its Apple Health integration does. Each app kept the promise it made. The duplication lives in the gaps between them, and so does the disagreement about calories.
The short answer
Every app that can write to Apple Health writes its own copy of a workout, and Apple Health keeps all of them. It ranks sources to avoid double-counting steps and active energy; it does not merge workouts. Record each session in one place, let one app forward it, and turn off "Workouts" write access for every other app under Health, Sharing, Apps.
The rest of this is how the copies get made, what we found when we measured 776 of them, why they do not agree with each other, and where our own handling was wrong.
How one session becomes three records
The paths are documented, individually, by the apps involved. Nobody documents the combination.
Garmin Connect can share data with Apple Health, and it can push activities to Strava through the standard partner sync. Strava, for its part, states that it "will automatically send route information, activity type, distance, time, and calories to Apple Health". So a single watch session has two routes into Apple Health: directly from Garmin Connect, and indirectly through Strava. Add the Apple Watch's own Workout app, or any third app with write access, and the number of copies goes up by one per writer.
Strava does guard against one loop. Its help centre is explicit: "Only activities recorded with the native Apple Workout app within the past 30 days can sync to Strava. Activities uploaded to Apple Health from a third-party app will not sync." That stops the Garmin workout, once in Apple Health, from bouncing back into Strava as a second activity. It is also why the guard runs in one direction only: it says nothing about Strava writing into Apple Health a workout Garmin already put there, which is the common case.
Apple Health does deduplicate, but for a different kind of data. For quantities like steps and active energy, it ranks sources, puts Apple Watch at the top by default, and uses the highest-ranked source where samples overlap. A workout is a different object. It is a discrete record with a start, an end, a type and an energy figure, and a second app writing one is treated as a second workout, not as a second opinion on the first. Developers hit the same thing from the other side: a plain HealthKit sample query returns everything from every source, and it is on the reading app to notice that two of them describe one event.
What we have not done is trace which app wrote each Apple Health copy on our roster, because we do not store the writing app's name. Everything below is measured from the rows themselves.
What 776 activities look like
We keep every wearable activity we receive in one table, keyed by source. Here is the roster as of this week, before any deduplication.
| Source | Rows | Carry a start time |
|---|---|---|
| Garmin | 390 | 0 |
| Apple Health | 263 | 263 |
| Strava | 123 | 0 |
That middle column is our own limitation and it matters later: Garmin and Strava both send a start time, and our importer truncates it to a date. We will come back to that.
Twenty-seven athletes have at least one wearable row. Ten of them have two or more sources connected, and those ten are where the copies live.
| Rows received | Real sessions after dedup | Copies | |
|---|---|---|---|
| Ten multi-source athletes | 363 | 225 | 138 (38%) |
| Seventeen single-source athletes | 413 | 412 | 1 |
For one athlete with Garmin and Apple Health, 109 rows collapse to 46. For another with all three sources, 45 collapse to 17, which is what you would expect if most of his runs are held three times. Two of the ten lose no rows at all, because their two sources happened never to cover the same session.
The calorie side is the one that changes decisions. Summing every row as received gives those ten athletes 162,746 kcal of measured burn. Summing one row per real session gives 92,502. The naive total is 76% too high across the group, and the per-athlete overstatement runs from 0% to 169%. Small roster, ten people, and two of those ten are responsible for most of the copies. It is not a population figure. It is what a "total calories this month" card would have shown them.
The copies do not agree with each other
If the copies were identical, the fix would be trivial: drop any row that matches another exactly. They are not identical, and the ways they differ are the interesting part.
We matched every same-day pair of rows from different sources whose calorie figures were within 25% of each other, which gave 68 pairs: 45 Garmin with Apple Health, 19 Garmin with Strava, 4 Apple Health with Strava.
| Agreement between the two copies | Pairs |
|---|---|
| Duration within 2 minutes | 64 of 68 |
| Same activity type after normalising labels | 45 of 68 |
| Calories within 10% | 31 of 68 |
Duration survives the trip almost perfectly. Type survives two thirds of the time. Calories survive less than half the time. Three separate things are going on.
The label changes on the way through
Garmin files a lifting session as strength_training. In seven of our pairs the Apple Health copy of that same session, same calories to the unit, same duration to the second, is filed as Other. HealthKit has a traditionalStrengthTraining type; the copy did not use it. Elsewhere Garmin's fitness_equipment sits opposite Apple Health's TraditionalStrengthTraining, and Garmin's indoor_cycling opposite a plain Cycling. Strava's WeightTraining and Padel opposite Garmin's strength_training and paddelball are the same problem in the other direction.
None of these labels is wrong. They are different vocabularies, and any app that decides "same session" by comparing type names will keep both copies.
Correction: the Strava gap was ours, not Strava's
Added 10 September 2026. The section below originally said Strava recalculates run and ride calories, and gave the table as evidence. The table is real. The explanation was wrong, and the error was on our side. Our Strava importer read the API's kilojoules field, Energy Output, as the calorie figure for any activity that carried it. Strava's separate calories field, which the list endpoint does not return, had the Garmin figure in it unchanged: in 12 of 12 Garmin uploads we later checked, it matched Garmin Connect to the unit. So "Garmin 252, Strava 334" was Garmin's calories beside Strava's kilojoules, and Strava had never recalculated anything. The full account, including why the field appears on runs at all, is in Strava's kilojoules are not calories. The original text follows, with its claims left in place so the correction is checkable.
What we originally called Strava recalculating calories
Across the nineteen Garmin-Strava pairs the pattern splits cleanly by sport. Seven padel sessions, one weight-training session and two indoor-cardio sessions copied through with identical calories. Every run and ride did not.
| Session | Garmin | Strava | Difference |
|---|---|---|---|
| Run, 55 min | 385 | 456 | +18% |
| Run, 34 min | 252 | 334 | +33% |
| Run, 88 min | 646 | 855 | +32% |
| Run, 15 min | 174 | 215 | +24% |
| Ride, 5h 49m | 1,543 | 1,680 | +9% |
| Ride, 7h 35m | 2,145 | 2,386 | +11% |
| Ride, 6h 31m | 2,012 | 1,932 | -4% |
We originally attributed that to Strava's documented calorie calculation for ride, run, walk and hike activities. It is not. Every row in the table is Garmin's calorie figure beside Strava's kilojoules, which our importer was storing as calories, and the padel and weight-training sessions "copied through identically" only because they carry no kilojoule field and so fell through to the real calorie read. The consequence for the reader is the same in one respect: a Strava copy of a Garmin run may carry a different number, and an app comparing calories to decide "same session" has to allow for it. The cause is not Strava having a second opinion; it is a field that is not a calorie count being read as one. We also wrote about strength sessions arriving from Strava with no calories at all, which is the same list-endpoint omission seen from the other side.
Garmin and Apple Health: two different shapes of the same mirror
The 45 Garmin-Apple Health pairs come in two distinct kinds, and they belong to different athletes.
For the first athlete, every pair matches on calories to the unit: 281 and 281, 336 and 336, 875 and 875. What differs is the label, which is where the Other filings above come from.
For the second athlete, 26 pairs, the label is right and the calories are not. His Apple Health copy carries the correct Running or TraditionalStrengthTraining type, the duration matches Garmin's to the second, and the calorie figure is 9% to 25% lower every time. A 60-minute strength session is 400 kcal in Garmin and 333 in Apple Health. A 20-minute indoor ride is 109 and 84. A 95-minute run is 1,191 and 1,071.
Divide the gap by the minutes and it stops looking random. For him it is 1.1 to 1.3 kcal per minute across every session, whatever the sport. For a second athlete with the same shape it is 1.04 to 1.06 kcal per minute across three runs. For a third it is 1.65 to 1.68 across four. A constant per minute, per person, is the signature of a resting metabolic rate: roughly what each of them would burn lying still for that long.
So the likely story is that one side reports total calories for the window and the other reports active calories, the work above resting. That inference fits the arithmetic on three athletes and we have not confirmed it against either vendor's documentation, so treat it as a reasonable read rather than a finding. It is at least a warning against "correcting" one figure toward the other, since both are internally consistent and simply measure different things.
What a copy costs downstream
The doubled calorie card is the visible damage and the least important.
Anything that counts sessions inherits the copies. A habit like "three runs a week" reads six. A weekly recap reports a training week that did not happen, and we have already written about what happens when an inflated count becomes next week's bar. A coach reading "you trained four times" from a log that holds two sessions twice will progress a plan on a volume nobody did. A wearable-first athlete, the one who never types a workout into anything, is exactly the athlete whose entire training history is made of these rows.
And the disagreement compounds the duplication. If the copies matched, the count would be wrong and the calories merely doubled. Because they do not match, an app that averages, or takes the larger, or takes whichever arrived last, produces a number that is not any device's number at all.
What we do about it, and where we got it wrong twice
Our rule for "these two rows are one session" runs in three steps, in order. If both rows carry a start time, time is authoritative: overlapping by at least half is the same session, and nothing else is consulted. If one side has no time, the rows must share a date and then either share a normalised type, or match on the physical signature: calories within 10% or 20 kcal, and duration within two minutes. The survivor is the highest-precedence source, Apple Health over Garmin over Strava, but it inherits the other copy's more specific label and any heart-rate or distance detail it was missing, so dropping the Garmin row never loses the Garmin data.
Two things went wrong with that rule, and both are worth knowing if you build anything like it.
The first: it applied itself within a single source. Everything after the time check exists to catch the cross-source mirror, and we ran it on rows from the same source too. A provider does not report one workout twice in one feed, so "same day, same type" from one source is not a duplicate. It is a person who walked twice. On one Garmin athlete it merged 26 of her 79 activities and discarded about 4,100 kcal, her 283 kcal 73-minute walk and her 85 kcal 24-minute walk on the same day becoming one event. The rule now refuses to merge same-source rows unless calories and duration both match, which is the one same-source duplicate that can genuinely exist: a provider revising an activity's calories and thereby minting a second row under a new key.
The second: we throw the time away. Look again at the table above. Zero of 390 Garmin rows and zero of 123 Strava rows carry a start time, and that is our importer, not the vendors. Both send it. So the time-is-authoritative branch never runs for those sources, and every Garmin and Strava row falls through to the label-or-signature rule.
We have not fixed that yet, and the reason is instructive. We simulated threading the timestamp through and running the real matcher over one athlete's real rows. With correct times the result is identical to today. With the naive local timestamp, which carries no offset and reads two hours off for a Paris athlete once stored, the same athlete's month goes from 24 sessions and 6,233 kcal to 37 sessions and 10,346 kcal, a silent 66% inflation. The strict time branch, fed a shifted time, stops recognising the mirror it exists to catch. A fix that lands the wrong timestamp is worse than no fix, so it waits for a careful pass.
What to do
- Pick one recorder per session. If the watch records it, let the watch's app forward it. Do not also start a workout on the phone, and do not let two apps both write to Apple Health.
- Turn off the second writer. Health, Sharing, Apps, choose the forwarding app, toggle Workouts off. It keeps working; it stops filing copies. If heart rate is doubled too, toggle that off for the same app.
- Compare calories, not labels, when you suspect a duplicate. Two rows with different type names and identical duration are almost certainly one session. Two rows with the same name and different calories may also be one session, if one of the apps is reading a different field as the burn.
- Do not reconcile the calorie figures by averaging. They measure different things. Pick the source you trust for that sport and read its number.
- Be suspicious of a session count that flatters you. Nobody reports a bug that credits them with extra training, which is why this one survives.
A workout is recorded once. Everything after that is a copy, and each copy is made by software that is doing its own job correctly.
Kipp merges your wearable activities across Apple Health, Garmin and Strava so one session counts once, keeps the most specific label and the measured detail from whichever copy had it, and never invents a calorie figure a device did not report.
Sources
- Strava's Apple Health integration: what Strava writes to Apple Health, the native-Workout-app-only rule for syncing back to Strava, and "Strava does not allow duplicate activities". Strava Help Center
- Strava's calorie calculation applying to ride, run, walk and hike, with other types showing the uploading partner's figure, as covered in our earlier piece. Strava says you burned zero calories
- Garmin's Apple Health sharing and the step-count discrepancy it documents. Garmin Support: Sharing Your Garmin Connect Data With Apple Health, Apple Health Shows a Different Step Count Than Garmin
- The duplicate-workout mechanism and the settings path to turn off an app's Workouts write access. Athlytic: How to Fix Duplicate Workouts in Apple Health
- Apple Health source prioritisation for overlapping data and its behaviour with workouts from several apps. Cult of Mac, Apple Community, Garmin Forums
- HealthKit returns samples from every source on a plain query, with
HKSourceRevisionidentifying the writer. Apple Developer: HKSourceRevision, Apple Developer Forums - The 776-row table, the 363-to-225 collapse, the 76% overstatement, the 68 matched pairs and the per-minute calorie gaps are from our own roster data, measured on 4 September 2026. Twenty-seven athletes, ten with more than one source, and most of the copies come from two people. None of this is a population claim.
