Skip to content

fix(mobile): suppress DOFEK-MOBILE-19 HealthKit background timeouts - #2351

Merged
Asherlc merged 15 commits into
mainfrom
Asherlc/bujumbura
Jul 31, 2026
Merged

Asherlc merged 15 commits into
mainfrom
Asherlc/bujumbura

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Suppress transient background HealthKit fetch timeouts (DOFEK-MOBILE-19) from Sentry across observer sync, workout route pushes, and a SDK beforeSend filter.
  • Recognize timeout messages on nested TRPCClientError causes and skip reporting when sync result.errors are only transient timeouts.
  • Lazy-init the HealthKit observer coordinator so expiration handling can safely reference sync-in-progress state.

Test plan

  • pnpm exec vitest run packages/mobile/lib/health-kit-errors.test.ts packages/mobile/lib/background-health-kit-sync.test.ts packages/mobile/lib/telemetry.test.ts packages/mobile/lib/health-kit-sync.test.ts

Made with Cursor

Summary by Sourcery

Suppress transient HealthKit background timeout errors from Sentry and treat them as non-actionable during mobile sync flows.

Bug Fixes:

  • Stop reporting transient HealthKit background fetch timeouts, including nested TRPC client errors, to Sentry for background sync and workout route uploads.
  • Avoid treating observer sync results that consist only of transient timeout errors as failures, and instead mark them for retry on the next delivery.

Enhancements:

  • Introduce shared helpers to detect transient network timeout messages and apply them across HealthKit sync workflows.
  • Adjust HealthKit iOS observer coordination to lazily initialize the update coordinator so expiration handling can safely reference sync-in-progress state.
  • Add a Sentry beforeSend filter that drops events originating from transient HealthKit network timeouts.

Tests:

  • Add unit tests covering transient timeout detection, nested error cause handling, Sentry beforeSend filtering, and background HealthKit sync behavior.

Summary by cubic

Suppress transient HealthKit background fetch timeouts (DOFEK-MOBILE-19) by treating them as retryable and filtering them from Sentry only for HealthKit-tagged events. Also restores Full sync on provider cards and moves web reports to server-owned emptyState.

  • Bug Fixes

    • Retry on timeout-only errors for observer sync and workout route pushes; log and skip telemetry (handles nested TRPC timeouts).
    • Scope @sentry/react-native beforeSend to drop transient network errors only for HealthKit sources (bg-healthkit-sync, health-kit-*).
    • Ignore observer sync results that contain only transient timeouts; mark for retry on next delivery.
    • Initialize the iOS HealthKit observer coordinator on module create with lock-guarded access for safe expiration handling.
    • Restore “Full sync” on connected provider cards; pass sinceDays: undefined.
    • Align weekly/monthly web reports with server-owned emptyState; use createReportEmptyState from dofek-server/report-empty-state and switch to WeeklyReportData/MonthlyReportData.
    • Update the catch-telemetry CI policy to recognize handleWorkoutRouteError as a canonical reporter.
  • Refactors

    • Centralize workout route error handling and tighten timeout detection to the specific “fetch failed … the request timed out” shape.
    • Remove unused web report-empty-state-fixtures.ts after switching to server createReportEmptyState.

Written for commit 3ebb24c. Summary will update on new commits.

Review in cubic

Treat transient background fetch timeouts as retryable noise across observer sync, route pushes, and Sentry beforeSend, and lazy-init the HealthKit observer coordinator for safe self capture.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 31, 2026 01:28
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sourcery-ai

sourcery-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR centralizes transient HealthKit network timeout detection, suppresses their reporting to Sentry in background sync flows and SDK telemetry, and adjusts iOS observer coordination to safely reference sync-in-progress state while adding targeted tests around the new behavior.

Sequence diagram for Sentry beforeSend HealthKit timeout suppression

sequenceDiagram
  actor MobileApp
  participant SentrySDK as Sentry
  participant HealthKitErrors

  MobileApp->>SentrySDK: initTelemetry()
  SentrySDK->>SentrySDK: init(beforeSend)

  MobileApp->>SentrySDK: captureException(error)
  SentrySDK->>HealthKitErrors: isBackgroundHealthKitTransientNetworkError(error)
  alt error is Error
    HealthKitErrors->>HealthKitErrors: isTransientNetworkErrorMessage(error.message)
    alt message is timeout
      HealthKitErrors-->>SentrySDK: true
    else message not timeout
      alt error.cause exists
        HealthKitErrors->>HealthKitErrors: isBackgroundHealthKitTransientNetworkError(error.cause)
        HealthKitErrors-->>SentrySDK: true/false
      else no cause
        HealthKitErrors-->>SentrySDK: false
      end
    end
  else error not Error
    HealthKitErrors->>HealthKitErrors: isTransientNetworkErrorMessage(String(error))
    HealthKitErrors-->>SentrySDK: true/false
  end

  alt isBackgroundHealthKitTransientNetworkError == true
    SentrySDK-->>MobileApp: beforeSend returns null (event dropped)
  else transient == false
    SentrySDK-->>MobileApp: beforeSend returns event (reported)
  end
Loading

File-Level Changes

Change Details Files
Detect transient HealthKit network timeout errors via shared helpers and support nested TRPC error causes.
  • Introduce isTransientNetworkErrorMessage to identify timeout-style fetch failures from message text.
  • Refactor isBackgroundHealthKitTransientNetworkError to reuse the message helper, inspect Error.cause recursively, and fall back to string messages.
  • Add unit tests validating timeout message detection and nested TRPCClientError cause handling.
packages/mobile/lib/health-kit-errors.ts
packages/mobile/lib/health-kit-errors.test.ts
Suppress transient timeout errors from Sentry in HealthKit sync and observer flows while still tracking them in sync results.
  • Wrap workout route push logic in syncHealthKitToServer and syncObserverWorkouts to skip captureException when errors match transient timeout patterns, but retain error strings in the returned results.
  • Update background HealthKit sync completion logic to filter out transient timeout error messages, only reporting actionable errors and logging a retry message when all errors are transient.
  • Extend background sync tests to cover non-reporting behavior for TRPCClientError and observer sync timeouts and verify logging and completion flags.
packages/mobile/lib/health-kit-sync.ts
packages/mobile/lib/background-health-kit-sync.ts
packages/mobile/lib/background-health-kit-sync.test.ts
Filter transient HealthKit timeout exceptions from Sentry via a beforeSend hook in the telemetry initialization.
  • Add isBackgroundHealthKitTransientNetworkError check in Sentry.init beforeSend callback to drop events originating from transient HealthKit network timeouts.
  • Extend telemetry tests to assert the presence of beforeSend and its behavior for timeout vs non-timeout errors.
packages/mobile/lib/telemetry.ts
packages/mobile/lib/telemetry.test.ts
Lazy-initialize the HealthKit observer update coordinator so expiration reporting can safely consult sync-in-progress state.
  • Change observerUpdateCoordinator from a stored constant to a lazy var while keeping observerSyncInProgress as a separate state flag.
  • Ensure the expiration report closure still captures self weakly and gates reporting on active observer sync state.
packages/mobile/modules/health-kit/ios/HealthKitModule.swift

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Asherlc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4dcd041c-af60-4b5c-a4db-e25edb154aee

📥 Commits

Reviewing files that changed from the base of the PR and between f5a85aa and 3ebb24c.

📒 Files selected for processing (19)
  • packages/mobile/app/providers/index.tsx
  • packages/mobile/app/providers/provider-card.tsx
  • packages/mobile/lib/background-health-kit-sync.test.ts
  • packages/mobile/lib/background-health-kit-sync.ts
  • packages/mobile/lib/health-kit-errors.test.ts
  • packages/mobile/lib/health-kit-errors.ts
  • packages/mobile/lib/health-kit-sync.test.ts
  • packages/mobile/lib/health-kit-sync.ts
  • packages/mobile/lib/telemetry.test.ts
  • packages/mobile/lib/telemetry.ts
  • packages/mobile/modules/health-kit/ios/HealthKitModule.swift
  • packages/server/package.json
  • packages/web/src/components/MonthlyReportContent.stories.tsx
  • packages/web/src/components/MonthlyReportContent.test.tsx
  • packages/web/src/components/WeeklyReportCard.stories.tsx
  • packages/web/src/components/WeeklyReportCard.test.tsx
  • packages/web/src/components/report-empty-state-fixtures.ts
  • scripts/mobile-catch-telemetry-policy.test.ts
  • scripts/mobile-catch-telemetry-policy.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The error-handling blocks in syncHealthKitToServer and syncObserverWorkouts for workout routes are now duplicated; consider extracting a shared helper (e.g., handleWorkoutRouteError) to centralize the isTransientNetworkErrorMessage logic and avoid repeated message extraction.
  • The transient timeout detection relies on specific substrings in isTransientNetworkErrorMessage; if these server messages change frequently, consider tightening the matching (e.g., via more structured error types or regex) to avoid accidentally classifying unrelated errors as transient.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The error-handling blocks in `syncHealthKitToServer` and `syncObserverWorkouts` for workout routes are now duplicated; consider extracting a shared helper (e.g., `handleWorkoutRouteError`) to centralize the `isTransientNetworkErrorMessage` logic and avoid repeated message extraction.
- The transient timeout detection relies on specific substrings in `isTransientNetworkErrorMessage`; if these server messages change frequently, consider tightening the matching (e.g., via more structured error types or regex) to avoid accidentally classifying unrelated errors as transient.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Suppress transient HealthKit background timeouts in mobile telemetry

🐞 Bug fix 🧪 Tests ✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Suppress transient HealthKit background fetch timeouts from Sentry across sync flows.
• Treat timeout-only observer sync failures as retryable, not actionable errors.
• Add shared timeout detection + tests, and lazy-init iOS observer coordinator for safe expiration
 handling.
Diagram

graph TD
  A["HealthKitModule (iOS)"] --> B["Background HK Sync"] --> C["HealthKit Sync"] --> G{{"tRPC API"}}
  B --> D["health-kit-errors"]
  C --> D
  E["Telemetry init"] --> F{{"Sentry"}}
  E --> D
  B --> E
  C --> E

  subgraph Legend
    direction LR
    _mod["Module"] ~~~ _ext{{"External"}} ~~~ _api{{"API"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use structured error codes instead of message matching
  • ➕ More robust than string matching across OS/SDK variants
  • ➕ Reduces risk of suppressing unrelated errors with similar text
  • ➖ May require changes in native layer, fetch implementation, or server error mapping
  • ➖ Not always available for lower-level network failures
2. Rate-limit / sample these events instead of dropping
  • ➕ Preserves some visibility if timeouts spike or regress
  • ➕ Less risk of hiding a real widespread outage
  • ➖ Still produces noise and can inflate Sentry volume
  • ➖ Harder to tune correctly across devices/background constraints
3. Centralize retryable error handling inside a single sync error policy layer
  • ➕ Avoids duplicating timeout checks across multiple sync call sites
  • ➕ Creates a clearer contract for what is actionable vs retryable
  • ➖ Larger refactor; higher short-term risk
  • ➖ May be overkill if only a small set of background flows are affected

Recommendation: The PR’s approach (shared transient-timeout detector + filtering at both call sites and Sentry beforeSend) is a pragmatic mitigation for DOFEK-MOBILE-19 with good test coverage. If these timeouts remain a recurring class of issues, consider evolving message matching into structured error classification (codes/types) to reduce false positives/negatives over time.

Files changed (8) +176 / -19

Enhancement (1) +14 / -2
health-kit-errors.tsAdd shared transient network timeout message helper + cause traversal +14/-2

Add shared transient network timeout message helper + cause traversal

• Extracts message-based timeout detection into isTransientNetworkErrorMessage. Extends isBackgroundHealthKitTransientNetworkError to recursively inspect Error.cause so wrapped errors are correctly classified as transient.

packages/mobile/lib/health-kit-errors.ts

Bug fix (4) +42 / -17
background-health-kit-sync.tsTreat timeout-only observer sync errors as retryable noise +9/-2

Treat timeout-only observer sync errors as retryable noise

• Filters observer sync result errors to separate transient timeout messages from actionable failures. If all errors are transient timeouts, it logs and returns failure without Sentry reporting so the delivery can retry on the next observer callback.

packages/mobile/lib/background-health-kit-sync.ts

health-kit-sync.tsSkip Sentry capture for transient route push timeouts +23/-13

Skip Sentry capture for transient route push timeouts

• Updates workout route push error handling in both full sync and observer sync paths to avoid captureException when the error is a transient timeout. Still records the error message for retry/reporting via sync result aggregation.

packages/mobile/lib/health-kit-sync.ts

telemetry.tsAdd Sentry beforeSend filter for HealthKit transient timeout errors +8/-0

Add Sentry beforeSend filter for HealthKit transient timeout errors

• Installs a beforeSend hook in Sentry.init that drops events when the original exception matches the HealthKit transient timeout detector. This prevents noisy background timeout events from reaching Sentry globally.

packages/mobile/lib/telemetry.ts

HealthKitModule.swiftLazy-init HealthKit observer coordinator to safely reference sync state +2/-2

Lazy-init HealthKit observer coordinator to safely reference sync state

• Moves observerSyncInProgress earlier and converts the observer update coordinator into a lazy property. This allows expiration handling closures to safely reference sync-in-progress state without early self capture issues during initialization.

packages/mobile/modules/health-kit/ios/HealthKitModule.swift

Tests (3) +120 / -0
background-health-kit-sync.test.tsAdd tests ensuring HealthKit timeout errors are not reported +72/-0

Add tests ensuring HealthKit timeout errors are not reported

• Adds coverage for suppressing Sentry reporting when background sync fails due to transient fetch timeouts, including nested TRPCClientError causes. Verifies observer updates are not completed (so they retry) and that timeout handling logs the expected retry message.

packages/mobile/lib/background-health-kit-sync.test.ts

health-kit-errors.test.tsIntroduce unit tests for transient timeout detection and nested causes +30/-0

Introduce unit tests for transient timeout detection and nested causes

• Adds tests validating timeout message matching and that transient network errors are detected on Error instances as well as via nested .cause chains (e.g., TRPCClientError wrapping).

packages/mobile/lib/health-kit-errors.test.ts

telemetry.test.tsTest Sentry beforeSend drops transient HealthKit timeout events +18/-0

Test Sentry beforeSend drops transient HealthKit timeout events

• Extends initTelemetry tests to assert a beforeSend function is installed. Validates timeout errors are dropped (null) while unrelated errors are still sent.

packages/mobile/lib/telemetry.test.ts

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

QR code for dofek://preview/pr-2351

Channel pr-2351
Deep Link dofek://preview/pr-2351
Commit fcae980

To test on device:

  1. Build and install the preview client: PREVIEW_CHANNEL=pr-2351 pnpm expo prebuild --clean -p ios
  2. Or tap deep link on an existing preview build: dofek://preview/pr-2351

Each PR gets its own channel. Build a preview client with PREVIEW_CHANNEL=pr-{N} to test.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for fcae9808 are ready:

This comment updates automatically on each PR push.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@qodo-code-review

qodo-code-review Bot commented Jul 31, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 171 rules

Grey Divider


Remediation recommended

1. Lazy coordinator init risk ✓ Resolved 🐞 Bug ☼ Reliability
Description
HealthKitModule.swift changes observerUpdateCoordinator to lazy, meaning it is initialized on
first access from either the HKObserverQuery callback or JS-exposed functions. If those code paths
trigger first access from different threads, initialization is no longer guaranteed serialized and
can become a concurrency hazard.
Code

packages/mobile/modules/health-kit/ios/HealthKitModule.swift[R42-45]

+    private var observerSyncInProgress = false
+    private lazy var observerUpdateCoordinator = HealthKitObserverUpdateCoordinator(
        timeout: 25,
        reportExpiration: { [weak self] expiration in
Relevance

●●● Strong

Team has accepted Swift thread-safety/concurrency hardening in HealthKit code previously (PR #764,
#1226).

PR-#764
PR-#1226

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The coordinator is now declared lazy, and the module accesses it from both the observer query
callback (register) and JS-exposed functions (complete/completeAll). This creates multiple
potential first-access points, increasing the risk that initialization happens from an unexpected
execution context.

packages/mobile/modules/health-kit/ios/HealthKitModule.swift[36-86]
packages/mobile/modules/health-kit/ios/HealthKitModule.swift[825-874]
packages/mobile/modules/health-kit/ios/HealthKitModule.swift[913-929]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`observerUpdateCoordinator` is now `lazy` and first access can occur from multiple entry points (observer callback vs JS bridge functions). If first access is not guaranteed to happen on a single serialized executor/queue, this introduces avoidable initialization race risk.

### Issue Context
The PR likely made this `lazy` to allow the expiration callback to reference `self.observerSyncInProgress`. You can keep that behavior while still ensuring the coordinator is constructed deterministically.

### Fix Focus Areas
- packages/mobile/modules/health-kit/ios/HealthKitModule.swift[42-76]
- packages/mobile/modules/health-kit/ios/HealthKitModule.swift[825-874]
- packages/mobile/modules/health-kit/ios/HealthKitModule.swift[917-929]

### Suggested fix
Force a single, deterministic initialization point before any possible concurrent access, for example:
- In the `setupBackgroundObservers` function (before executing any observer queries), touch the property once (e.g. `_ = self.observerUpdateCoordinator`) on the same known queue.
- Or, move coordinator construction into an explicit initializer/setup method guarded by your module’s serialization mechanism, and store it in a non-lazy property once created.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. TRPC timeout cause ignored ✓ Resolved 🐞 Bug ◔ Observability
Description
health-kit-sync.ts uses isTransientNetworkErrorMessage(error.message) to decide whether to
suppress reporting, which misses timeout errors that are only present on error.cause (e.g.,
TRPCClientError wrappers). This causes transient timeouts to still go through captureException in
these paths (even if later dropped by beforeSend), creating unnecessary telemetry/log noise and
inconsistent classification.
Code

packages/mobile/lib/health-kit-sync.ts[R353-363]

+        if (isTransientNetworkErrorMessage(error instanceof Error ? error.message : String(error))) {
+          const message = error instanceof Error ? error.message : String(error);
+          errors.push(`Push workout routes: ${message}`);
+        } else {
+          captureException(error, {
+            source: "health-kit-workout-route-push",
+            routeCount: routes.length,
+          });
+          const message = error instanceof Error ? error.message : String(error);
+          errors.push(`Push workout routes: ${message}`);
+        }
Relevance

●●● Strong

Team accepts HealthKit timeout/cause classification and Sentry-noise suppression patterns (PR
#1971).

PR-#1971
PR-#1873

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The affected catch blocks use only error.message for transient detection, while the new helper and
tests explicitly support detecting timeouts on nested error.cause. This mismatch means wrapper
timeouts will still hit the captureException branch in these health-kit-sync routes.

packages/mobile/lib/health-kit-sync.ts[343-365]
packages/mobile/lib/health-kit-sync.ts[525-544]
packages/mobile/lib/health-kit-errors.ts[23-34]
packages/mobile/lib/health-kit-errors.test.ts[22-28]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Workout-route push error handling suppresses reporting using only the top-level error message. Wrapper errors (e.g. TRPC-style errors) can keep the timeout message in `error.cause`, so the code still calls `captureException` for transient timeouts.

### Issue Context
The PR already introduced `isBackgroundHealthKitTransientNetworkError(...)` which recursively checks `Error.cause`, and tests assert that a wrapper with a timeout cause should be treated as transient.

### Fix Focus Areas
- packages/mobile/lib/health-kit-sync.ts[349-364]
- packages/mobile/lib/health-kit-sync.ts[525-542]
- packages/mobile/lib/health-kit-errors.ts[23-34]
- packages/mobile/lib/health-kit-errors.test.ts[22-28]

### Suggested fix
In both workout-route push `catch` blocks, replace the message-only predicate with the cause-aware helper:
- `if (isBackgroundHealthKitTransientNetworkError(error)) { ... } else { captureException(...) ... }`
This keeps suppression consistent with the background-sync path and avoids unnecessary `captureException` calls for wrapper timeouts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Broad Sentry timeout filter ✓ Resolved 🐞 Bug ◔ Observability
Description
telemetry.ts adds a global Sentry beforeSend that drops any event whose exception message matches
the transient-timeout substring check, without verifying the error actually came from HealthKit.
This can suppress unrelated fetch-timeout errors across the app and reduce production observability.
Code

packages/mobile/lib/telemetry.ts[R67-72]

+    beforeSend(event, hint) {
+      const error = hint.originalException;
+      if (isBackgroundHealthKitTransientNetworkError(error)) {
+        return null;
+      }
+      return event;
Relevance

●● Moderate

No clear precedent on scoping Sentry beforeSend drops by source/tag vs message-only filter.

PR-#1896
PR-#1971

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new beforeSend drops events whenever
isBackgroundHealthKitTransientNetworkError(hint.originalException) matches, but it does not check
the event’s source tag even though captureException attaches it. The underlying predicate is
only a message substring match (plus optional cause recursion), so it is not intrinsically
HealthKit-scoped.

packages/mobile/lib/telemetry.ts[64-76]
packages/mobile/lib/telemetry.ts[95-101]
packages/mobile/lib/health-kit-errors.ts[15-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
A global `beforeSend` filter drops all timeout-shaped errors based solely on the exception message (and `cause` chain), with no HealthKit-specific scoping. This can hide unrelated, actionable network timeouts.

### Issue Context
`captureException(...)` already attaches a `source` tag when provided, but the new filter ignores event tags/extra and only inspects the exception.

### Fix Focus Areas
- packages/mobile/lib/telemetry.ts[64-76]
- packages/mobile/lib/telemetry.ts[95-101]
- packages/mobile/lib/health-kit-errors.ts[15-34]

### Suggested fix
Update `beforeSend` to only drop when the event is known to originate from HealthKit background sync, e.g.:
- Check `event.tags?.source` (or `event.extra?.source`) for a HealthKit-specific value/prefix (e.g. `bg-healthkit-sync`, `health-kit-...`) before applying `isBackgroundHealthKitTransientNetworkError`.
- Alternatively, remove the global drop and rely on call-site suppression where `source`/context is explicit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. HealthKit timeout logic in TS 📘 Rule violation ⌂ Architecture
Description
New/modified TypeScript code implements HealthKit-related error classification and retry/telemetry
suppression logic (e.g., string-matching timeout messages and walking Error.cause). This violates
the requirement that BLE/HealthKit domain logic reside in Swift, increasing the risk of divergent
behavior across platforms and making the JS bridge more than “bindings only.”
Code

packages/mobile/lib/health-kit-errors.ts[R15-34]

+export function isTransientNetworkErrorMessage(message: string): boolean {
  const normalized = message.toLowerCase();
  return (
    normalized.includes("fetch failed") &&
    (normalized.includes("timed out") || normalized.includes("timeout"))
  );
}
+
+export function isBackgroundHealthKitTransientNetworkError(error: unknown): boolean {
+  if (error instanceof Error) {
+    if (isTransientNetworkErrorMessage(error.message)) {
+      return true;
+    }
+    if (error.cause !== undefined) {
+      return isBackgroundHealthKitTransientNetworkError(error.cause);
+    }
+    return false;
+  }
+  return isTransientNetworkErrorMessage(String(error));
+}
Relevance

● Weak

Team previously rejected “TS bridge-only” compliance request; keeps BLE/HealthKit orchestration in
TS (PR #2026).

PR-#2026
PR-#1971

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 721993 requires TypeScript to be bridge-only for BLE/HealthKit, with business rules
and retry/error interpretation implemented in Swift. The added functions in health-kit-errors.ts
implement HealthKit-specific error interpretation (timeout detection and recursive cause handling),
and the modified sync/telemetry flows use that logic to decide retry behavior and suppress Sentry
reporting.

Rule 721993: BLE and HealthKit domain logic must reside in Swift (TypeScript is bridge-only)
packages/mobile/lib/health-kit-errors.ts[15-34]
packages/mobile/lib/background-health-kit-sync.ts[87-123]
packages/mobile/lib/telemetry.ts[63-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds HealthKit-related domain logic in TypeScript (timeout detection via message parsing and nested-cause traversal), but the compliance rule requires BLE/HealthKit domain logic to live in Swift with TypeScript acting as a bridge only.

## Issue Context
The current implementation makes HealthKit sync behavior depend on JS-only heuristics (string matching and cause recursion). To comply, move this decisioning/classification into native Swift and expose only the minimal bridged API/flags to TypeScript.

## Fix Focus Areas
- packages/mobile/lib/health-kit-errors.ts[15-34]
- packages/mobile/lib/background-health-kit-sync.ts[87-123]
- packages/mobile/lib/health-kit-sync.ts[350-363]
- packages/mobile/lib/health-kit-sync.ts[528-541]
- packages/mobile/lib/telemetry.ts[63-73]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread packages/mobile/lib/telemetry.ts
Comment thread packages/mobile/lib/health-kit-sync.ts Outdated
Comment thread packages/mobile/modules/health-kit/ios/HealthKitModule.swift Outdated
Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Use the canonical is-error guard pattern for transient network failures and apply biome formatting needed for CI lint.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Pass fullSync through handleSyncProvider and render the Full sync action on connected provider cards so routine and full-history sync call the correct sinceDays values.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Align infrastructure error test with the rate-limit fetch timeout message introduced for transient provider transport failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Use WeeklyReportData and MonthlyReportData in report components, export createReportEmptyState from dofek-server, and update tests and shared-report parsing for the required emptyState field.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Resolve conflicts in HealthKit observer sync, health-report tests,
and web report fixtures while keeping createReportEmptyState usage
for component-level WeeklyReportCard and MonthlyReportContent types.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@Asherlc
Asherlc enabled auto-merge (squash) July 31, 2026 02:31
Only drop transient network errors in beforeSend when the event is
tagged with a HealthKit background sync source, avoiding suppression
of unrelated fetch timeouts elsewhere in the app.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Apply biome formatting to telemetry and health-kit-errors files, and
pass sinceDays: undefined when handleSyncProvider is called with fullSync.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

codereviewbot-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

LGTM! The PR cleanly addresses scoped Sentry timeout filtering for HealthKit background syncs, filters non-actionable transient timeouts before reporting observer sync errors, adds full sync capabilities to provider cards, and refactors report empty states across web and server components.


🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

…edback

Initialize observerUpdateCoordinator on module create with lock-guarded
access instead of lazy var, and add a test that TRPC timeout wrappers
during route push skip captureException via cause-aware detection.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

… match

Extract handleWorkoutRouteError for shared query/push error handling in
full and observer sync paths, and match transient fetch timeouts with a
regex targeting the DOFEK-MOBILE-19 request timed out shape.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Extend the catch telemetry linter to treat handleWorkoutRouteError as a
canonical error reporter, and restructure route query catches to throw
locked-device errors before delegating to the shared helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Resolve conflicts in HealthKit observer coordinator and report empty-state
fixtures by combining lock-guarded coordinator init with main's breadcrumb-only
expiration handling and server-owned emptyState in shared reports.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

codereviewbot-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

LGTM!

Key Changes Reviewed:

  • Telemetry & Sentry Filtering: Scoped transient fetch timeout suppression in Sentry's beforeSend hook specifically to HealthKit background sync sources (bg-healthkit-sync and health-kit-*), resolving broad suppression concerns while recursively handling TRPC client error causes.
  • Provider Cards: Added support for manual full sync actions (sinceDays: undefined).
  • iOS Native Module: Replaced lazy var initialization with lock-guaranteed thread-safe instantiation (observerUpdateCoordinatorLock) for native HealthKit observer query callbacks.
  • Telemetry Policy Script & Web Stories: Updated AST checks for handleWorkoutRouteError and simplified report empty state storybook/test fixtures.

🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

Knip flagged report-empty-state-fixtures.ts as unused after stories and
tests switched to createReportEmptyState from dofek-server.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@Asherlc
Asherlc merged commit da321af into main Jul 31, 2026
100 checks passed
@Asherlc
Asherlc deleted the Asherlc/bujumbura branch July 31, 2026 16:49
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