Sleep Analysis captured into raw archive but never summarized into sleep output object
App: health.md (iOS)
Export tiers tested: Summary and Detailed Time-Series (reproduced at both)
Export formats tested: JSON, Markdown
Data source: Colmi R12 ring via PulseLoop (xyz.sakshambhutani.pulseloop2)
Summary
Sleep Analysis data is present in HealthKit and gets correctly captured into the raw healthkit_record_archive.records array, but is never aggregated into a summarized sleep object in the export. Every other populated category (heart, vitals, activity, workouts, mindfulness, mobility, hearing) gets a proper top-level summary object — sleep never appears, even when qualifying data exists. No error is logged for it in diagnostics.partial_failures.
Steps to reproduce
1. Confirm Health Metrics → Sleep is enabled in-app (my case: 8/8 sub-metrics on).
2. Confirm iOS Settings → Health → Data Access & Devices → health.md → Sleep and Resting Heart Rate are both granted read access (confirmed both ON).
3. Set Data Detail to Detailed Time-Series (also reproduced on Summary).
4. Export for a date with a confirmed full night of sleep in the Health app (my case: night of 2026-09-21 → 2026-09-22).
5. Inspect the exported JSON.
Expected
A sleep object at the top level, parallel to heart/vitals/activity, with total sleep, bedtime, wake time, and a deep/core/REM/awake stage breakdown — matching the sleep_* metric IDs shown in the app's own Health Metrics screen.
Actual
No sleep key anywhere in the export. But healthkit_record_archive.query_manifest.results shows the underlying HealthKit query for HKCategoryTypeIdentifierSleepAnalysis on 2026-09-22 succeeded with record_count: 40, and healthkit_record_archive.records contains all 40 of those records, in full detail:
- 16× asleepDeep, 12× asleepCore, 10× asleepREM, 2× awake
- Correctly timestamped 2026-09-22T04:03Z → 2026-09-22T11:52Z
- Correctly attributed to source PulseLoop / device Colmi R12
- Each record is individually pre-tagged with metric_attribution.direct_metric_ids listing exactly which summary fields it should populate: sleep_deep, sleep_core, sleep_rem, sleep_awake, sleep_total, sleep_bedtime, sleep_wake, sleep_in_bed
The aggregation step that should turn those pre-tagged raw records into the summary object appears to simply not run for this type, and fails silently.
Confirms this isn't a windowing bug: for 2026-09-23, the same query against a correctly-bounded midnight–midnight window legitimately returned record_count: 0 (no sleep session started that day) — so the underlying HealthKit query and date-window logic work correctly. This is specifically a gap in the summarization step once qualifying raw records do exist.
Discussion1
Follow-up: exact expected field names, per the app's own generated docs
Checked metric-catalog.md (generated from production serializers, per /docs/reference/generation/) — it confirms exactly which fields should have appeared and didn't:
┌────────────────────┬────────────────────────────┐
│ Metric ID │ Documented export key │
├────────────────────┼────────────────────────────┤
│ sleep_total │ sleep_total_hours │
├────────────────────┼────────────────────────────┤
│ sleep_bedtime │ sleep_bedtime │
├────────────────────┼────────────────────────────┤
│ sleep_wake │ sleep_wake │
├────────────────────┼────────────────────────────┤
│ sleep_deep │ sleep_deep_hours │
├────────────────────┼────────────────────────────┤
│ sleep_rem │ sleep_rem_hours │
├────────────────────┼────────────────────────────┤
│ sleep_core │ sleep_core_hours │
├────────────────────┼────────────────────────────┤
│ sleep_awake │ sleep_awake_hours │
├────────────────────┼────────────────────────────┤
│ sleep_in_bed │ sleep_in_bed_hours │
├────────────────────┼────────────────────────────┤
│ │ resting_heart_rate (Heart │
│ resting_heart_rate │ category, "mostRecent" │
│ │ aggregation) │
└────────────────────┴────────────────────────────┘
None of these keys appear anywhere in either of my exports (schema v8, matches current), despite healthkit_record_archive.records containing 40 correctly-tagged HKCategoryTypeIdentifierSleepAnalysis records with metric_attribution.direct_metric_ids pointing to exactly these IDs. Since the catalog is generated from the same serializers that ship the export, this looks like a real gap between the documented/generated contract and the actual output, not a documentation-vs-behavior ambiguity.