Skip to content

Troubleshooting

Symptoms, causes and fixes. coachd’s log is docker compose logs coachd; it carries IDs, outcomes and error classes, never health data. For the watch app’s own messages, see pairing and status messages.

Garmin sync

get_status shows each data type’s last_outcome and age_hours, and the key’s days_left. request_sync answers with an outcome and next_allowed_at.

SymptomCauseFix
garmin_rate_limited; log garmin rate limitedGarmin answered 429. coachd pauses every Garmin call until its Retry-After (at most 24 h).Wait: next_allowed_at says until when.
request_sync says rate_limitedThe agent asked again within COACHD_SYNC_MIN_INTERVAL (10 min).Wait until next_allowed_at.
request_sync says already_runningA sync is already running.Ask again shortly.
auth_failedGarmin refused the key: expired or revoked. garmin_key.days_left is negative once expired.Renew the key on the page.
A stale_data warning on a proposalNo successful sync for over 24 h.Check get_status and the log; the GarminSyncFailing alert fires after 3 h.
get_activity returns no laps or serieshas_fit is false: the FIT file isn’t stored yet.It downloads after the activity syncs; ask again later.
“Garmin refused this key; nothing changed” on Garmin keyThe new key is wrong or not active yet.Copy it again from Garmin’s page. coachd keeps the key it had.

Starting coachd

coachd checks its settings at start and exits with a message naming the setting.

MessageFix
bind source path does not exist (from compose)A secret file is missing. Create every file in the table, empty where allowed.
secret file … has mode …; group/other access must be removedWrite it again with put_secret.
COACHD_PUBLIC_AUTH=builtin: leave COACHD_ACCESS_* emptyEmpty the three COACHD_ACCESS_* settings.
COACHD_PUBLIC_URL: builtin mode needs https://, or http://localhost for developmentUse your https:// hostname; an IP address doesn’t work with passkeys.
COACHD_NTFY_URL requires the public laneSet COACHD_PUBLIC_URL and a public lane mode, or leave ntfy off.

docker compose ps shows coachd unhealthy when its /livez probe fails: read the log.

Connecting an agent

SymptomCauseFix
claude.ai: “Couldn’t register with … sign-in service”Access mode: the app’s redirect URI isn’t allowed.Add it under Managed OAuth.
claude.ai or ChatGPT can’t connect, but your browser canBot Fight Mode or a WAF challenge blocks their servers.See Expose coachd.
“coachd could not identify the app that asked to connect”Built-in mode: the app doesn’t identify itself with a client metadata document coachd can fetch, and DCR is off.Turn on DCR for that app, or use the private lane.
“The app did not give a return address registered for it”The app’s redirect URI doesn’t match the one it registered.Start the connection again from the app.
“No passkey is enrolled, so nothing can approve this connection”No passkey yet.Enrol one, then connect again.
“This connection request has expired”The consent page was open too long.Start again from the app.
An app stops working; ntfy says coachd revoked a connectionBuilt-in mode: a token was reused, so coachd revoked the connection.Reconnect the app; check Connected apps.
Private lane: 401The token expired (--ttl, 90 days by default) or was revoked.token list, then create a new one.
A write tool fails with “insufficient scope”The token or app has read scope only.Create a --scope write token, or reconnect and keep write on the consent page.

Passkeys and approval

SymptomCauseFix
“enrolment link invalid or expired”The link is older than 10 minutes or was used. In Access mode, a link opened while signed out loses its token.Run coachd enroll again; in Access mode sign in first, then open the link in that browser.
Signing in or approving on a new deviceThat device has no passkey for coachd.Run coachd enroll and open the link on the new device. Revoke old ones on Passkeys.
“this approval expired; reload the page”An approval is valid for 24 h at most, and never past the workout’s date.Reload and approve.
“this proposal is cancelled”, or another statusIt changed after the page loaded: the agent cancelled it, or it was decided in another tab.Reload: you approve exactly what you see.
“this workout’s date has passed”A pending proposal expires at the end of its date.Ask the agent for a new one.
No ntfy noticeCOACHD_NTFY_URL unset, or a new token not picked up (it is read only at start). Log: notification failed.See ntfy; docker compose up -d --force-recreate coachd.

The watch

SymptomCauseFix
An approved workout isn’t on the watchThe app downloads only when you open it. Background checks notify, never download.Open the app, or Sync now. It fetches workouts from today to 14 days ahead.
Status delivery_failedThe watch fetched it but didn’t confirm within 15 minutes.The next sync retries.
The same workout twiceA FIT file copied over USB and the app’s own copy.Delete the USB copy: see one delivery path.
A prompt instead of a notification, no glanceNotifications and the glance need fēnix 7 or later.See compatibility.

For an alert, see what it means under Alerts.

Still stuck? Open an issue with your version, your public lane mode and the log lines, with tokens and health data removed.

Last updated on