Skip to content

fix(igpsport): persist deduplicated activity identities - #58

Merged
jorge-huxley merged 5 commits into
jorge-huxley:masterfrom
KirillSugonyakin:codex/activity-idempotency
Sep 17, 2026
Merged

jorge-huxley merged 5 commits into
jorge-huxley:masterfrom
KirillSugonyakin:codex/activity-idempotency

Conversation

@KirillSugonyakin

Copy link
Copy Markdown
Contributor

The iGPSPORT activity sync currently retries the same FIT indefinitely when Intervals.icu content-hash deduplication returns an existing activity owned by another integration. The upload is counted as new, but no durable ride-to-activity identity is saved.

This change persists iGPSPORT ride mappings, distinguishes HTTP 201 creations from HTTP 200 existing-content links, verifies mapped activities before skipping, and fails closed when identity discovery is unavailable. It preserves existing external IDs and applies activity type only to newly created activities.

Validation: 242 pytest tests pass on Python 3.13; malformed discovery and upload response regressions are covered.

@jorge-huxley

Copy link
Copy Markdown
Owner

Hi! Thank you. I'm currently on vacation, I will have a look next week.

@jorge-huxley

Copy link
Copy Markdown
Owner

Which other integration was involved when you encountered this? I haven't seen this behavior with Strava.

@KirillSugonyakin

KirillSugonyakin commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

It wasn't Strava. The activity that reproduced this had already been imported into Intervals.icu through Intervals Companion (source=OAUTH_CLIENT, oauth_client_name=Intervals Companion) from the same iGPSPORT 802 FIT. Its existing external_id was the FIT filename rather than the expected igpsport_{ride_id}.

When intervalssync later uploaded the original FIT, Intervals.icu returned HTTP 200 with the existing activity ID via content-hash deduplication. Because discovery only matched the expected iGPSPORT external ID, the same FIT was downloaded and submitted again on every cycle.

I verified the fix in the deployed relay: the first run linked the existing activity once, and subsequent natural runs skipped it without another download or upload. The public relay documentation describes this duplicate-prevention case here: https://github.com/KirillSugonyakin/igpsport-intervals-relay/blob/main/docs/guides/synchronization.md#duplicate-prevention

@jorge-huxley

Copy link
Copy Markdown
Owner

Thanks for the detailed explanation about Intervals Companion; that makes the repeated-upload case clear.

I've added e62ea9a to this PR to connect the activity mappings to the GUI too. The desktop/mobile app now saves and reloads the mappings, applies verified deletions, and includes linked activities in its sync summary. This follows the CLI persistence approach already in your change.

The regression test exercises the GUI sync button, reloads settings to simulate an app restart, and verifies that the next sync skips the linked activity without another FIT download or upload. All 267 tests pass on Python 3.13.

This commit is limited to the GUI integration. I'll look separately at preserving successful mappings when a later ride raises a network exception.

@jorge-huxley

Copy link
Copy Markdown
Owner

Added a separate follow-up commit, cf30e22, for the network-error case mentioned above. Both CLI and GUI now save confirmed activity mappings immediately after a successful upload/link, before setting the sport type or processing the next ride. Verified discovery/pruning results are also saved before FIT processing begins.

Previously, a timeout on a later ride could prevent the sync from returning, so earlier successful links were never saved and were uploaded again next time.

Regression tests cover download, upload, and sport-type timeouts through both front-ends, then reload settings and verify the completed ride is skipped. All 275 tests pass on Python 3.13.

@jorge-huxley
jorge-huxley merged commit 8e604e7 into jorge-huxley:master Sep 17, 2026
1 check passed
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.

2 participants