originalException.
Base type
| Class | Code | Trigger |
|---|---|---|
PermissionDeniedError | PERMISSION_DENIED | OS or adapter permission denied. |
DeviceUnavailableError | DEVICE_UNAVAILABLE | No wearable / source available. |
NetworkError | NETWORK_ERROR | Cloud adapter HTTP/RPC failure. |
originalException carries the platform-specific underlying error so callers can inspect when the typed code is too coarse.
Permission denied
Triggered by:- HealthKit refusing access to a requested
HKObjectType. - Health Connect missing a permission grant for
HeartRateRecord(etc.). - BLE permission revoked at OS level.
- Vendor OAuth flow cancelled by user or rejected by vendor.
requestPermissionsreturningdeniedfor any requested type.
- For OS permissions: surface a UI rationale and call
requestPermissionsagain with the missing types. - For OAuth: route the user back through the vendor’s auth URL.
- For revoked permissions: the SDK reports
ConsentStatus.revoked, distinct fromdenied(never granted vs previously granted).
Device unavailable
Triggered when the adapter cannot find a viable data source:- HealthKit not present (iPad without paired iPhone).
- Health Connect not installed or unsupported on the Android version.
- BLE HRM scan times out without finding a 0x180D device.
- Vendor webhook subscription not active.
streamHRinvoked while no underlying source is connected.
streamHR/streamHRV reconnects, but readMetrics() calls fail fast. Hosts should display a “no device connected” UI and let the user reconnect.
Network error
Triggered by HTTP/RPC failures in the cloud adapters (WHOOP, Garmin, Fitbit). TheoriginalException wraps the underlying transport error.
Common causes:
- Vendor token expired (caller should refresh OAuth).
- Vendor rate-limit (HTTP 429); adapters back off.
- Server-side outage (5xx).
- TLS validation failure.
PermissionDeniedError instead, depending on the vendor.
Adapter-specific errors
HealthKit
| Code | Cause |
|---|---|
HKErrorAuthorizationDenied | User denied at HealthKit prompt. |
HKErrorAuthorizationNotDetermined | Not asked yet. |
HKErrorNoData | Sample query returned empty. |
HKErrorRequiredAuthorizationDenied | Required type missing in HKHealthStore request. |
PermissionDeniedError or DeviceUnavailableError as appropriate.
Health Connect
| Code | Cause |
|---|---|
INSTALL_REQUIRED | Health Connect not installed on the device. |
MIGRATION_REQUIRED | User must migrate from older Google Fit. |
NO_PERMISSIONS | Permission grant missing. |
DeviceUnavailableError (install/migration) or PermissionDeniedError (permissions).
BLE HRM
| Failure | Surface |
|---|---|
| Bluetooth permission denied | PermissionDeniedError("Bluetooth"). |
| Adapter off | DeviceUnavailableError("Bluetooth disabled"). |
| Scan timeout (no 0x180D) | DeviceUnavailableError("No HRM found"). |
| Connection lost mid-stream | streamHR emits a NetworkError-shaped event, then attempts reconnect. |
| Device sends malformed packet | Sample dropped, stream continues. |
WHOOP (cloud)
| Failure | Surface |
|---|---|
| OAuth token expired | PermissionDeniedError; caller refreshes via WhoopProvider. |
| Webhook subscription invalid | DeviceUnavailableError; caller re-subscribes. |
| RAMEN delivery channel closed | Caller-transparent reconnect; final close emits no error. |
fetchRawDataForFlux returns 4xx | NetworkError with the vendor’s response in originalException. |
Garmin (cloud)
| Failure | Surface |
|---|---|
| OAuth token expired | PermissionDeniedError. |
| Webhook arrived but REST pull (DeliveryHint.ping) fails | NetworkError; no WearMetrics emitted for this event. |
| Rate-limited (429) | Adapter backs off; caller sees no error unless persistent. |
Garmin Health SDK (RTS)
| Failure | Surface |
|---|---|
| License missing | DeviceUnavailableError("Garmin Health SDK not licensed") at initialize. |
| Pairing failed | DeviceUnavailableError. |
| RTS connection drop | Auto-reconnect; logged. |
Apple Health XML backfill
| Failure | Surface |
|---|---|
export.zip malformed | SynheartWearError("Invalid Apple Health export"). |
| XML parse error | Same; the underlying XmlParserException is in originalException. |
| Runtime backfill ingest fails | Bubbles up the runtime’s negative return code. |
Logging policy
The SDK logs atinfo/warn and never logs:
- Vendor OAuth tokens.
- Personal
device_ids in plain (debug builds may; production ones do not). - Raw HRV/RR sample bytes.
- XML payloads beyond malformed-record line numbers.
WearMetrics snapshots are not logged. Hosts that need observability should collect them at the consumer layer.
Validation invariants
Regardless of error state:WearMetrics.timestampis always UTC ISO-8601.- Numeric metric values are finite (NaN/inf get filtered to
null). - Event ordering is preserved within a single adapter; cross-adapter ordering uses
timestamp.
Related
- Wear Overview — config flags that influence error paths.
- Adapters — per-vendor flow.
- Data Schema — null-vs-missing semantics.