Repository navigation
Releases: stefiix92/coachd
Release list
v0.5.1
Added
- Demo mode (
docs/design/demo-mode.md):COACHD_DEMO=truerunsserveon made-up, deterministic Garmin data, with no Garmin key, so coachd can be tried with an agent in a few minutes (Try it with demo data). About 26 weeks of a plausible runner's activities, with real FIT files, plus sleep, HRV, resting heart rate, Body Battery and weight. A new migration marks a demo data directory: demo mode refuses a real one, a real coachd refuses a demo one, and restore refuses to swap one for the other. - Documentation site at stefiix92.github.io/coachd: install, agents, workouts, the watch app, operations and reference, with search, built with Hugo and the Hextra theme from
docs/and published to GitHub Pages by the newdocsworkflow. Every page is also served as Markdown, with anllms.txtindex, for agents reading the docs. - New documentation pages: troubleshooting, FAQ, compatibility (AI clients, watches and phones), data and privacy, coachd compared with alternatives, an example week of a runner and an agent, and starter instructions for your agent. The README gains the overview diagram.
- Watch app CI: the new
watchworkflow builds the watch app for every supported watch and runs its simulator tests on Linux; with aCIQ_DEVELOPER_KEYsecret, releases also attach the Connect IQ Store package. It needs theGARMIN_USERNAMEandGARMIN_PASSWORDsecrets for Garmin's device files and skips with a notice without them.
Changed
- The install guide (
deploy/README.md), the watch app guide (watch/README.md) and the Grafana guide (deploy/grafana/README.md) moved to the documentation site; those files now point to its pages. The README is shorter and links to the site.
Security
- Built with Go 1.27.3, which fixes GO-2026-6809:
crypto/x509now distrusts compromised intermediate certificates. govulncheck reports the affected code reachable from passkey sign-in. Images up to 0.5.0 were built with Go 1.27.2; upgrade to the next release.
Fixed
- FIT files with an empty header CRC: some Zwift rides write a 14-byte FIT header whose optional CRC is 0x0000. The FIT decoder coachd uses then computed the file CRC without the header and rejected these valid files, so the ride had no laps, series or perceived effort ("fit detail failed", error class
other). coachd now checks FIT integrity as Garmin's FIT SDK does; truncated and damaged files are still rejected. Affected rides fill in on the next sync after upgrading.
Image
ghcr.io/stefiix92/coachd@sha256:0f4d7238d4629a0727336007404dda40799b40d1b8c83d9da4d105bbd1d55ed1
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:0f4d7238d4629a0727336007404dda40799b40d1b8c83d9da4d105bbd1d55ed1 \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.5.0
Added
- Trainer workouts on the watch (
docs/design/trainer-workouts.md):- Watt targets: a
powerstep target takeslow_wandhigh_w(1 to 1500 W) instead oflow_pct_ftpandhigh_pct_ftp, for cycling. The FIT file carries them as watts, the page and the watch showpower 200–220 W, andget_activityreads them back from a ride's planned steps. A workout with a watt step is not exportable to Zwift, which takes % of FTP. - Indoor cycling: the new workout sport
indoor_cyclingis a ride on a smart trainer. The FIT file marks it indoor, so the watch starts it as an indoor ride, where a paired trainer can follow the power targets. It is stored and listed as cycling and exports to Zwift like any cycling workout.
- Watt targets: a
Image
ghcr.io/stefiix92/coachd@sha256:020a892c08d9155674e542533d4916c1814b11eef2fdea30f1d7ca85377cdfa8
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:020a892c08d9155674e542533d4916c1814b11eef2fdea30f1d7ca85377cdfa8 \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.4.0
Added
- Watch app v2 (
docs/design/watch-v2.md):- The main screen shows the next workout: its day, name, sport, duration and distance; the sync status moves to a footer.
- START opens a menu that lists the workouts from today on; selecting one shows its steps, notes and the agent's rationale, and START there begins it.
- Agent messages: the new MCP tool
send_message(1 to 300 characters, write scope) stores a short display-only message. The watch shows the latest five from the last seven days under Messages, with an unread count on the main screen. Messages go nowhere else, not to ntfy. - Background checks: every 15 minutes while paired, the watch asks coachd for new messages and workouts and notifies you (a notification on fēnix 7 and later, an "open coachd?" prompt on older watches). It never downloads in the background: open the app to sync. A new Notifications setting in the Connect IQ app turns it off.
- Glance on fēnix 7 and later: the next workout and unread messages.
- The app needs two new permissions, Background and Notifications.
- Device API:
GET /device/v1/workouts/{id}(a workout's detail) andGET /device/v1/messages, both read-only. An older watch app ignores them, and the new app works against an older coachd without details or messages. - Planned vs actual (
docs/design/workout-outcome.md):- For an activity recorded from a structured workout on the watch,
get_activitywith detaillapsorseriesaddsworkout: the steps the watch followed, and each executed step with its time, distance, heart rate, pace, power and, for a heart-rate or pace range, the seconds inside, below and above it. Numbers only, computed from the activity's FIT file at ingest. workout.proposal_idlinks the activity to the delivered proposal it carried out, andlist_workoutsgainsactivity_idson each workout. The link needs the steps the watch recorded to equal the workout coachd delivered, and the dates to be within 3 days.- Garmin Coach and Garmin Connect workouts get the steps too, without a link. Zwift-recorded rides do not: their files carry no step index.
- Not yet checked on a workout delivered by the coachd watch app: if the watch rewrites the steps, the steps still show and only the link is missing.
- For an activity recorded from a structured workout on the watch,
- Approve the week (
docs/design/approve-week.md): when several workouts are waiting, Review all (/review) shows each one in full, then approves all of them with one passkey prompt.- The passkey ceremony binds every workout's ID and content hash; the approval is all or nothing in one transaction, and any change since the page was shown refuses it with the day of the workout that changed.
- Left out, with the reason: workouts whose date has passed, whose content does not match its hash, or that another waiting workout replaces; at most 20 at a time.
- The home page links it when two or more are waiting, and a waiting workout's page says how many more are waiting.
- Renew the Garmin key on the page (
docs/design/garmin-key-renewal.md): Garmin key (/garmin, linked from the home page, which warns 14 days before expiry) takes a new key and its validity (30, 90 or 180 days, or 1 year). coachd checks it with Garmin, stores it in the data directory (garmin-key.json, mode 0600) and switches at once, without a restart;get_statusand the key-expiry metric follow. If Garmin refuses the key, nothing changes. The key is never shown, logged or backed up, and the audit log recordsgarmin_key_renewedwith its outcome. - Notes that expire (
docs/design/expiring-notes.md):upsert_notetakes an optionalvalid_until(today up to 366 days ahead) for facts that stop being true. After that dayget_notesleaves the note out unlessinclude_expiredis true, and reportsexpired_count; each note showsvalid_untilandexpired. Expired notes are never deleted automatically and still count toward the 50-note cap; the "notes full" error says how many agent notes are expired. The notes page shows the date and fades expired notes. - Post-run webhook (
docs/design/post-run-webhook.md): withCOACHD_WEBHOOK_URLset, coachd POSTs{"event":"activity.stored","activity_id",...,"date","sport","proposal_id"}(IDs only) when an activity that started in the last 48 hours is stored, after its FIT file and per-step numbers are in, so your own automation can wake the agent for a post-run review. Optional bearer token fromCOACHD_WEBHOOK_TOKEN_FILE; retries like ntfy; new metriccoachd_webhook_sends_total{outcome}. - Your post-activity rating (
docs/design/perceived-effort.md):list_activitiesandget_activityshowperceived: {feel_pct, rpe}, the "How did you feel?" and RPE answers you give on the watch after a workout, read from the FIT file (the Personal API's JSON does not carry them). Null until the FIT file is read, or when you skipped the questions. Migration 0013 resets the detail cache once more so they fill in.
Changed
- The watch app's status no longer shows the workout count after a sync; the main screen shows "Last sync HH:MM" instead.
- Upgrade note: the activity detail cache (laps, series and now workouts) is reset by a migration and rebuilds from the stored FIT files: newest first, 20 activities per FIT drain, and on each
get_activitythat misses it.
Fixed
- Proposals that expired were not logged when the stale-delivery check after them failed, for example during shutdown. This also made a release test flaky.
Image
ghcr.io/stefiix92/coachd@sha256:84d978cab4df7170eb3692c47229c32fcf0f9f9af3f99c61d6de07656b8ed43c
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:84d978cab4df7170eb3692c47229c32fcf0f9f9af3f99c61d6de07656b8ed43c \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.3.0
Added
- Power targets for cycling:
propose_workouttakes apowerstep target in % of FTP (low_pct_ftp,high_pct_ftp, 20 to 300, cycling only). The approval page shows it, and the FIT file carries it to the watch. - Download for Zwift: an approved cycling workout whose steps all have a time duration downloads as a Zwift workout file (
.zwo) from its page; copy it into Zwift's Workouts folder on a Mac or PC (see the README).- Power steps become steady-state blocks at the middle of the band; a two-step power repeat becomes a Zwift interval set. Heart-rate, pace, cadence and effort targets become free-ride blocks with an on-screen message.
- The first download of either file, FIT or
.zwo, marks the workout delivered. - The
.zwofiles were not tried in Zwift before this release. Text is kept to plain ASCII to be safe. If Zwift does not list a downloaded workout, please open an issue.
- Warning
zwift_not_exportable: a cycling workout with a distance, open or heart-rate-bounded step gets a warning naming the step. It never blocks. - Telemetry: workout downloads carry a
formatattribute (fitorzwo).
Changed
- The cadence target's schema text says it is rpm for cycling.
Image
ghcr.io/stefiix92/coachd@sha256:6ab9458d446b3329c4d92fd4c30d5c897791d9cca4aa7dd30c73db0f088ab2dd
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:6ab9458d446b3329c4d92fd4c30d5c897791d9cca4aa7dd30c73db0f088ab2dd \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.2.0
Changed
- Runner page redesign: a new look for every page, still without JavaScript frameworks or a build step.
- The coachd logo, a vector drawing, in the header and on the sign-in page, and its navy and green colours throughout; on the dark theme the logo's navy turns light. The browser tab shows the logo on a white tile (SVG, with a PNG for browsers without SVG icons); the home-screen icon is the logo on white.
- Navigation tabs with icons that fit a phone screen and mark the current page; Connected apps is a tab in builtin mode.
- Proposals appear as cards with a date badge, a warning count or a coloured status.
- The home page shows a countdown to the goal.
- Each workout step has a colour for its kind (warm-up, active, recovery, rest, cool-down) and guides for repeats.
- On a phone the Approve and Decline buttons stay in view while the workout scrolls.
- Agent-written text keeps its label, now with its own colour and a ✦ mark.
- Notes fold their edit form away.
- Sign-in, enrolment, consent and errors are centred cards.
- Light and dark themes use
light-dark(), and page changes cross-fade in browsers that support view transitions.
- Watch app:
- The coachd URL is a setting in the Connect IQ phone app instead of being compiled in, so one Connect IQ Store app serves every runner. Sideloaded builds use
COACHD_PUBLIC_URLas its default. - The pairing is bound to that URL: changing it forgets the token and the pairing in progress before the app talks to another server, and the pairing screen shows the host.
- Supports fēnix and Forerunner models with app workouts (
watch/devices.sh). build.sh storeandbuild.sh store-betabuild the Store packages; sideload, test and beta builds use a separate dev app id ("coachd dev").- An installed watch app pairs again once after updating.
- The coachd URL is a setting in the Connect IQ phone app instead of being compiled in, so one Connect IQ Store app serves every runner. Sideloaded builds use
Image
ghcr.io/stefiix92/coachd@sha256:beb23ee71a6aa3d7ffabe06233be519a4daea8346650583458228909cc3d5cc2
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:beb23ee71a6aa3d7ffabe06233be519a4daea8346650583458228909cc3d5cc2 \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.comv0.1.0
First public release.
Added
- Data in:
- Garmin Connect Personal API ingest and backfill: activities with their original FIT files, sleep, HRV, resting HR, Body Battery and stress.
- Deterministic summaries: numbers only, never verdicts.
- MCP server (spec 2026-07-28 and 2025-11-25):
- read tools;
request_sync;- goal and notes;
propose_workoutwith sanity checks and warnings, andcancel_proposal.
- Approval and delivery:
- The approval page with passkey (WebAuthn) approval.
- Minimal ntfy notifications.
- FIT download.
- Connect IQ watch app with device pairing and delivery confirmation.
- Network lanes:
- The private lane over Tailscale, with scoped static tokens.
- The public lane in Access mode (Cloudflare Access with Managed OAuth).
- The public lane in built-in mode: passkey sessions and coachd's own OAuth 2.1 server, with CIMD, opt-in DCR, PKCE, rotating refresh tokens and reuse detection.
- Operations:
- backups and restore;
- OpenTelemetry with an allowlist-only redaction policy;
- a local Grafana profile, dashboards and alerts;
- a hardened compose stack.
- Releases: signed multi-arch images on ghcr.io, with an SBOM and build provenance.
Image
ghcr.io/stefiix92/coachd@sha256:612bdd6d88f16238c39d8c176b6d26dbd15695e659ab9a838c4d9ae5db585239
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:612bdd6d88f16238c39d8c176b6d26dbd15695e659ab9a838c4d9ae5db585239 \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com