Skip to content

Auto-detected workouts never reach the sessions table — invisible to AI Coach (v_sessions) and Apple Health export despite both documenting they include them #131

Description

@dannymcc

Symptom (shared root behind both #129 and #130)

Reported 22 Jul 2026 (user RR, 0.9.19+50): today's completed workout does not appear. Two faces:

  • AI Coach says the latest v_sessions row is yesterday and there is no session for today.
  • Edge does not write today's workout to Apple Health, even after a forced sync.

#129 and #130 explain these as independent bugs for a manual session (a sessions row that exists but is mis-dated / not yet exported). This issue is the third defect and the single root that unifies both symptoms when the workout is auto-detected rather than manually started in-app — the dominant path for a WHOOP-style band where the user never taps start/stop.

Root cause: auto-detected workouts are never written to the sessions table, yet both the coach view and the HealthKit export read only that table — while documenting that they include auto/detected workouts

Detected workouts live only in the derived day_result bundle under detected_workouts:

  • Produced by the derivation engine into the bundle (lib/compute/derivation_engine.dart:3065, and the detected_workouts field noted at :2497).
  • Surfaced to the UI by merging them at read time in LocalRepositoryImpl.getSessions() (lib/data/local_repository_impl.dart:1016-1032), which unions sessionsInRange (manual) with detected_workouts decoded from each recent bundle. The comment there is explicit: "Manual/live sessions (the sessions table) MERGED with auto-detected workouts from the per-day bundle."

Nothing ever promotes a detected workout into the sessions table. The only LocalDb.putSession callers are the manual live-session paths — app_state.startWorkout/stopWorkout (lib/state/app_state.dart:2960, :3088), LocalRepositoryImpl.startWorkout/endWorkout (:1899, :1917), and the legacy cloud_import (lib/cloud/cloud_import.dart:258). None of them write detected bouts.

But the two other consumers of workout data read the sessions table exclusively — and both claim, in their own docs/comments, to include auto/detected workouts:

  1. AI Coach reads v_sessions, a raw passthrough of the sessions table (lib/data/db.dart:1171-1175). The coach system prompt documents it as "v_sessions(…) — workouts (manual/live/auto)" (lib/coach/coach_prompt.dart:52; column list mirrored at lib/coach/coach_engine.dart:711). The "auto" is false — v_sessions can never contain an auto-detected workout. So the coach genuinely sees no row for today and reports yesterday's last manual session as the latest. (This is independent of AI Coach mis-dates workouts: v_sessions has no local-day column, so 'today's session' resolves in UTC #129: even with a local date column added, an auto workout still isn't in the table to be dated.)

  2. HealthKit / Health Connect export writes workouts only inside _exportDay, from LocalDb.sessionsInRange(...) (lib/health/health_export.dart:542-571) — and the comment above it reads "Workouts (manual/live/detected) finalized in this calendar day." sessionsInRange is a sessions-table query (lib/data/db.dart:3621-3632), so detected workouts are never written. A forced sync cannot help: healthSyncNow()exportAll()_exportDaysessionsInRange (app_state.dart:357), same table. (Independent of Today's workout not written to Apple Health: HealthKit export is day_result-gated, not session-triggered #130's day-result gating: even if the day exports, a detected workout isn't in the queried table.)

So getSessions() (the workouts screen) shows today's detected workout, while the coach and Apple Health both silently omit it — one gap, both symptoms.

Shared-root verdict

If RR's workout was auto-detected (no in-app start/stop), this single defect explains both symptoms and they share a root. If it was a manual session that persisted, #129 (coach mis-dating) and #130 (export day-gating) apply instead and the two are independent. The auto-detected case is the more likely real-world scenario for a band, and is the only hypothesis consistent with the coach seeing no row at all (a persisted manual done session would still appear in v_sessions, merely mis-dated).

Recommended fix (confidence: high that this is a real gap; medium that it is RR's specific case)

Make detected workouts first-class in the same store the coach and health export read. Either:

  1. Promote confirmed detected bouts into the sessions table at derive/finalise time with source='auto', deduped against overlapping manual spans exactly as getSessions() already does (local_repository_impl.dart:1013-1014,1029). Then v_sessions and the health export need no change. Cleanest — one source of truth.
  2. Or merge detected_workouts into both consumers the way getSessions() does: give v_sessions a UNION over the latest bundle's detected bouts, and have _exportDay also write detected workouts for its day. More code, two extra merge sites to keep in sync.

Fixing the two doc/comment claims (coach_prompt.dart:52 "auto", health_export.dart:538 "detected") is not optional either way — today they assert coverage the code does not provide.

Needs on-device confirmation


Related: #129 (coach v_sessions missing local-day column — manual-session variant), #130 (HealthKit export day-result-gated — manual-session variant). This issue is the auto-detected-workout root that unifies both symptoms; the three can all be true at once.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions