Aller au contenu

Dépannage

Cette page regroupe les problèmes courants et leurs solutions.

Par où commencer

Avant de chercher plus loin, lancez le diagnostic d'installation en une commande — il vérifie les tokens Garmin, le MCP, la configuration, le profil athlète, l'index et le daily-sync sans rien modifier :

python3 scripts/coach_doctor.py

Voir le skill coach-doctor pour le détail de chaque vérification et la sortie --json.

Installation

uv introuvable après installation

export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"

Rechargez votre shell (source ~/.zshrc ou source ~/.bashrc) puis relancez le script.

garmin-mcp introuvable

uv tool install --python 3.12 git+https://github.com/Taxuspt/garmin_mcp@cfc5d799ab0f165e837f1188a1d093c65838aaf7

leanproxy-mcp introuvable (mode passerelle uniquement)

brew tap mmornati/leanproxy-mcp
brew install leanproxy-mcp

Note

leanproxy-mcp n'est requis qu'en mode passerelle (--use-leanproxy). En mode direct (défaut), il n'est pas installé.

Authentification Garmin

Tokens expirés

Les tokens Garmin sont valides environ 6 mois. Pour les renouveler :

uv run garmin-mcp-auth

coach doctor (garmin_token) estime cette échéance à partir de la date de dernière modification de garmin_tokens.json, faute d'échéance explicite dans ce fichier (voir le skill coach-doctor) — cette estimation peut dériver, car le fichier est réécrit à chaque rafraîchissement automatique du token, ce qui repousse sa date de modification sans que la session ait réellement été renouvelée pour 6 mois de plus.

Vérifier les tokens

uv run garmin-mcp-auth --verify

Erreur de connexion à Garmin Connect

  1. Vérifiez que garmin-mcp fonctionne : garmin-mcp stdio
  2. Vérifiez que les tokens existent : ls ~/.garminconnect/
  3. Relancez l'authentification : uv run garmin-mcp-auth

Alerte push avant expiration des tokens (#32)

Avant chaque exécution, scripts/daily-sync.sh interroge coach_doctor.py --check garmin_token --json (lecture seule, borné dans le temps via timeout/gtimeout quand l'un des deux est disponible — sur une machine sans coreutils, l'appel n'est pas borné, mais ce check ne fait aucun accès réseau ; dans tous les cas, une panne du diagnostic se journalise et n'empêche jamais la synchronisation) et envoie, au plus une fois par jour, une notification ntfy avec la commande de renouvellement quand l'échéance estimée approche.

  • Seuils : [notifications].token_alert_days dans config/workspace.toml (défaut [14, 3], en jours restants, entiers positifs uniquement — toute valeur non entière dans la config est ignorée avec un avertissement, jamais évaluée). Le seuil le plus proche de l'échéance (le plus petit, ainsi que « expiré ») alerte chaque jour jusqu'au renouvellement ; un seuil plus lointain (J-14 par défaut) n'alerte qu'une seule fois tant que l'échéance n'a pas atteint le seuil suivant — pas de rappel quotidien dès J-14. Une liste vide ([], ou qui ne contient plus aucun entier valide) désactive l'alerte d'expiration, sans effet sur la notification 401 explicite ci-dessous. « Expiré depuis N jour(s) » arrondit vers le bas comme coach doctor (un token expiré depuis 30h30 affiche 1 jour, pas 2).
  • Désactivation : [notifications].token_alerts = false dans config/workspace.user.toml. Sans effet si provider = "none".
  • État : <workspace>/logs/.token-alert-state (gitignoré comme tout logs/) — supprimez-le pour forcer une réévaluation, par exemple après un renouvellement manuel des tokens en dehors du daily-sync.
  • 401 réel : si la synchronisation rencontre effectivement un refus d'authentification Garmin — jamais un 401 sans rapport, par exemple un échec d'authentification du runner Codex lui-même, ni une ligne ERREUR qui mentionne « tokens Garmin » en passant pour une tout autre raison (panne réseau, DNS…) —, la notification remplace le message d'échec générique par un message explicite avec la commande de renouvellement. Deux formes reconnues selon l'exécuteur : le texte réel de garminconnect/garmin_mcp (GarminConnectAuthenticationError, visible seulement si l'exécuteur restitue la sortie brute d'un outil MCP, ex. codex exec en mode verbeux — un « 401 » est alors une certitude, revendiqué tel quel) et la formulation ERREUR : … que skills/garmin-daily-sync/SKILL.md impose à l'agent d'écrire lui-même, reconnue SEULEMENT quand elle nomme explicitement une expiration/un refus de token ou renvoie vers garmin-mcp-auth — seule forme qui traverse le runner par défaut, claude -p --output-format text (ne restitue que le message final de l'agent) ; dans ce cas, la cause réelle n'étant pas confirmée comme un 401, la notification relaie la ligne de l'agent telle quelle plutôt que d'affirmer « 401 ». Si une alerte d'expiration est déjà partie plus tôt dans le même run, cette notification n'est pas doublée.

Synchronisation sur l'abonnement Claude : « You've hit your weekly limit »

Avec runner = "claude" sans [sync].api_key_env (défaut), la synchronisation consomme votre forfait Claude. Quand il atteint sa limite, claude -p s'arrête (code 1, « You've hit your weekly limit · resets … » dans logs/sync-AAAA-MM-JJ.log) et la notification « ⏳ Sync Garmin — limite de l'abonnement Claude atteinte » part (une fois par jour au plus). Rien n'est cassé : le passage suivant réessaie, et la sync reprend quand la limite se renouvelle. Pour qu'elle ne dépende plus du forfait :

python3 scripts/arc_llm.py sync anthropic

Clé ANTHROPIC_API_KEY dans ~/.config/ai-running-coach/llm.env d'abord, puis la sync passe sur l'API (facturée au jeton, plafonnée par [sync].daily_budget_eur) sans toucher au chat. Détails : Changer l'IA de la synchronisation.

Synchronisation sur une API (OpenRouter, Anthropic)

Quand [sync].api_key_env est défini, ces échecs ont leur propre notification — ils ne sont jamais présentés comme un problème Garmin :

Notification Cause Correctif
🔑 Sync Garmin — clé openrouter refusée (ou Anthropic) Le fournisseur renvoie 401 : clé invalide, révoquée ou mal copiée. Éditez ~/.config/ai-running-coach/llm.env (ligne OPENROUTER_API_KEY=…, mode 600), puis python3 scripts/coach_doctor.py --check llm_config.
💳 Sync Garmin — crédits … épuisés Le fournisseur renvoie 402 : plus de crédit. Rechargez le compte du fournisseur.
🔑 Sync Garmin — clé API absente [sync].api_key_env est défini mais la variable est introuvable dans llm.env et l'environnement. Ajoutez la ligne dans llm.env (./install.sh --llm … crée le fichier), ou revenez à l'abonnement : python3 scripts/arc_llm.py sync claude-subscription.
💸 Sync Garmin suspendue — budget atteint Le cumul du jour (logs/.sync-spend-AAAA-MM-JJ) atteint [sync].daily_budget_eur. Le run est sauté, sans erreur. Relancez demain, ou relevez ./install.sh --sync-budget EUR.
⚠ hors contrat arc : … (dans le résumé) Un fichier écrit par le modèle pendant le run ne respecte pas le bloc arc. Voir logs/sync-AAAA-MM-JJ.log ; changez de modèle ([sync].model) si cela se répète.

Avec l'essai gratuit (--llm openrouter-free, modèle :free, limites) :

Notification Cause Correctif
⏳ Sync Garmin — quota gratuit du jour atteint 429 avec un motif « par jour » : 50 requêtes par jour sans achat de crédits (1 000 après 10 $). Rien à faire : le compteur suit le jour UTC, le passage suivant réessaie. Ou achetez 10 $ de crédits une fois. python3 scripts/arc_llm.py free-quota donne le reste du jour.
⏳ Sync Garmin — modèle gratuit saturé 429 par minute (20 requêtes/min), fournisseur du modèle saturé, ou 429 sans motif identifiable (cela peut aussi être le quota du jour). Rien à faire, le passage suivant réessaie.
⏳ Sync Garmin — fournisseur du modèle saturé 503 / provider_overloaded : le fournisseur amont du modèle est surchargé (observé le 2026-10-15 sur le modèle gratuit par défaut, « Upstream error from Nvidia: Service temporarily overloaded », renvoyé avec un HTTP 200). Ni votre quota, ni votre clé, ni vos crédits. Vaut aussi pour un modèle payant OpenRouter (message sans « gratuit »). Rien à faire, le passage suivant réessaie ; si cela dure plusieurs heures, choisissez un autre modèle ([sync].model).
🔒 Sync Garmin — réglage de confidentialité … incompatible 404 « ZDR violation » ou « data policy » : votre réglage de confidentialité OpenRouter (entraînement / rétention / ZDR) exclut tous les fournisseurs du modèle. Le plus probable : vous avez refusé les fournisseurs qui s'entraînent, ce qui exclut NVIDIA (modèle gratuit par défaut). Avec ZDR imposé, Gemma 4 a répondu 404 sur le compte utilisé pour nos essais. Choisissez un autre modèle (gratuit : Gemma 4, non testé ; payant : ./install.sh --llm openrouter) ou ajustez les réglages de confidentialité.

Le chat affiche les mêmes cas en clair (« Quota gratuit du jour atteint … », « Le fournisseur du modèle gratuit est saturé … », « … réglage de confidentialité … ») ; un fournisseur saturé sur un modèle payant donne « Le fournisseur de ce modèle est saturé … ». Les motifs « par jour » / « par minute » du texte d'erreur sont une approximation du projet : OpenRouter ne documente pas le corps exact d'un 429 sur les modèles gratuits ; un 429 sans motif reconnu est présenté comme « limite … atteinte » en citant les trois causes possibles.

Avec un abonnement ChatGPT ou Copilot (--llm chatgpt|copilot), la synchronisation ne lance jamais de connexion ; ses notifications :

Notification Cause Correctif
🔑 Sync Garmin — reconnectez ChatGPT (ou GitHub Copilot) Jeton absent du magasin du coach, expiré (Token refresh failed) ou refusé (401/403). Dans un terminal : python3 scripts/arc_llm.py login chatgpt (ou copilot). Vérification : python3 scripts/arc_llm.py auth-status chatgpt.
🔑 Sync Garmin — forfait ChatGPT insuffisant usage_not_included : l'offre ChatGPT Free n'est pas acceptée. ChatGPT Plus ou Pro, ou une autre IA (Choisir son IA).
⏳ Sync Garmin — limites du forfait … atteintes insufficient_quota / limite d'usage du forfait. Rien à faire : elles se renouvellent d'elles-mêmes ; le passage suivant réessaie.
⏳ Sync Garmin reportée Le chat tenait le verrou du magasin plus de 5 minutes. Rien à faire, le passage suivant réessaie.

opencode introuvable : curl -fsSL https://opencode.ai/v2/install | bash. Un refus d'authentification Garmin garde sa notification habituelle (🔑 Authentification Garmin refusée, voir plus haut).

La connexion IA

Le coach ne répond pas ? Avant de chercher, un seul réflexe : tester la connexion.

  • App macOS : bouton Tester la connexion, à côté de « Parler au coach ».
  • Page du chat : bouton Tester la connexion sous la zone de saisie.
  • Terminal : python3 scripts/arc_llm.py check --probe (--json pour une sortie machine).

Le test lit la configuration réelle ([chat] et [sync], workspace.toml puis workspace.user.toml), reconnaît le fournisseur de chacun (OpenRouter, essai gratuit, ChatGPT, GitHub Copilot, Anthropic, API compatible OpenAI, assistant sur abonnement) et rend une liste de points ✅ / ⚠️ / ❌ / ℹ️, chacun avec la commande exacte qui corrige. Sans --probe, rien n'est envoyé sur le réseau (présence de la clé, droits des fichiers, jeton d'abonnement, OpenCode). Avec --probe, un appel minimal par fournisseur : GET /api/v1/key chez OpenRouter (clé valide, crédit restant, requêtes gratuites du jour), la liste des modèles chez Anthropic et chez un fournisseur compatible OpenAI. Une valeur de clé ou de jeton n'apparaît jamais, ni dans le tableau ni dans le JSON. Le code de sortie est 1 s'il y a au moins une erreur (❌), 0 sinon.

python3 scripts/coach_doctor.py --probe-llm reprend ce diagnostic dans le contrôle llm_config ; sans l'option, le doctor reste hors ligne.

Symptôme (ou message du chat) Ce que dit le test Que faire
« Clé API du fournisseur refusée (401) » ; le test dit « Clé OpenRouter refusée (401) » La clé est invalide, révoquée ou supprimée. python3 scripts/arc_llm.py connect openrouter (ou l'App : Reconnecter OpenRouter).
« Crédit du fournisseur insuffisant (402) » ; « Crédit OpenRouter insuffisant » ou « Crédit de la clé épuisé » Le compte (ou le plafond de la clé) n'a plus de crédit. Ajoutez du crédit : https://openrouter.ai/settings/credits.
« Quota gratuit du jour atteint » Les 50 requêtes gratuites du jour sont consommées (1 000 après 10 $ de crédits au total). Rien à faire : remise à zéro à minuit UTC. Ou ajoutez du crédit.
« Limite de débit du fournisseur atteinte » 429 : trop de requêtes en peu de temps (20 par minute sur les modèles gratuits). Attendez une minute et réessayez.
« Connexion ChatGPT absente du magasin du coach » ; « Connexion … expirée ou refusée » Pas de jeton oauth pour ChatGPT / Copilot dans le magasin du coach, ou OpenCode n'a pas pu le rafraîchir. python3 scripts/arc_llm.py login chatgpt --data-dir '<magasin>' (ou copilot) ; la commande exacte est dans le résultat du test.
« Jeton d'accès ChatGPT échu depuis … » (ℹ️) Normal : OpenCode rafraîchit le jeton d'accès (≈ 1 h) à la prochaine requête. Rien, sauf si le chat échoue ensuite : reconnectez alors ChatGPT (ligne précédente).
« Votre offre ChatGPT n'inclut pas cet usage » L'offre Free n'est pas acceptée. ChatGPT Plus ou Pro, ou une autre IA (Choisir son IA).
« Clé … absente » La variable n'est ni dans llm.env ni dans l'environnement. python3 scripts/arc_llm.py connect openrouter, ou ajoutez la ligne dans ~/.config/ai-running-coach/llm.env (mode 600).
« … est lisible par d'autres utilisateurs » llm.env, auth.json ou le magasin sont trop ouverts. La commande chmod 600 … (fichiers) ou chmod 700 … (dossier) affichée.
« OpenCode est introuvable » OpenCode n'est pas installé (ou pas dans le PATH de l'App). curl -fsSL https://opencode.ai/install \| bash.
« OPENCODE_DISABLE_DEFAULT_PLUGINS est défini » Cette variable d'environnement désactive les connexions ChatGPT / Copilot d'OpenCode. unset OPENCODE_DISABLE_DEFAULT_PLUGINS (et retirez-la de votre profil shell).
« … est injoignable (réseau coupé …) » (⚠️) Le test n'a pas pu joindre le fournisseur : la clé n'est ni validée ni refusée. Vérifiez la connexion Internet, puis relancez le test.
ℹ️ « Le chat (…) et la synchronisation (…) utilisent des fournisseurs différents » Information : chacun est vérifié séparément. Rien.

Limites assumées : il n'existe pas d'appel peu coûteux pour vérifier un abonnement ChatGPT ou Copilot (leur validité réelle ne se voit qu'à la première requête : le chat traduit alors l'erreur avec ces mêmes mots), et le solde d'une clé Anthropic n'est pas lisible par l'API (le test dit seulement que la clé est acceptée).

Configuration IDE

L'agent coach n'apparaît pas dans mon IDE

  • Claude Code : vérifiez que .claude/agents/ existe et contient coach.md
  • GitHub Copilot : vérifiez que .github/agents/ existe et contient coach.md, puis /agent dans Copilot CLI
  • OpenCode : vérifiez que .opencode/agents/ existe et contient coach.md
  • Gemini CLI : vérifiez que .gemini/commands/ contient les fichiers .toml

Relancez ./install.sh --ide <votre-ide> si nécessaire.

Le serveur MCP garmin (ou leanproxy) n'apparaît pas

Vérifiez la configuration MCP de votre IDE :

# Claude Code / GitHub Copilot
cat .mcp.json

# OpenCode
cat ~/.config/opencode/opencode.json

# Cursor
cat .cursor/mcp.json

# Windsurf
cat .windsurf/mcp_config.json

Chaque configuration doit contenir une référence au serveur MCP garmin (mode direct) ou leanproxy (mode passerelle).

Copilot CLI ne charge pas le serveur MCP garmin

Copilot CLI ne charge les serveurs MCP d'un projet qu'après confirmation de la confiance du dossier. Relancez copilot depuis la racine du projet, acceptez la demande de confiance, puis vérifiez avec /mcp.

Données

Les dossiers de travail sont vides

Les dossiers activities/, medical/, nutrition/, planning/, rapports/, gear/, resources/ sont créés par le script d'installation. Ils sont exclus du dépôt (voir .gitignore).

Les données Garmin ne se synchronisent pas

  1. Vérifiez que les tokens sont valides : uv run garmin-mcp-auth --verify
  2. En mode passerelle, vérifiez que le serveur garmin est configuré dans leanproxy : cat ~/.config/leanproxy_servers.yaml
  3. Vérifiez que garmin-mcp fonctionne : garmin-mcp stdio

Scripts Python

download_fit.py échoue avec une erreur de dépendances

Le script nécessite garminconnect et fitparse. Ces dépendances sont disponibles dans l'environnement garmin-mcp. Le script tente de se relancer automatiquement dans cet environnement. Si cela échoue :

uv tool run --from garminconnect --from fitparse python3 skills/fit-download/scripts/download_fit.py

Source Intervals.icu ([data].source = "intervals") : seul fitparse est requis, et seulement avec --json. Le script se relance dans l'environnement intervals-icu-mcp. Sur une installation antérieure à cette fonctionnalité, cet environnement n'a pas encore fitparse : relancez ./install.sh --source intervals, qui l'ajoute sans réinstaller le serveur.

Source COROS ([data].source = "coros") : le téléchargement passe par le relais du projet (aucune dépendance), et --json relance le script dans le lecteur de FIT dédié, ~/.local/share/ai-running-coach/fit-reader. S'il manque : ./install.sh --source coros le crée (/coach-doctor, contrôle fit_reader). « COROS non connecté » ou « jeton refusé » : python3 scripts/arc_coros_auth.py login dans un terminal.

Le coach ne trouve pas icu_get_wellness_for_date (ou, à l'inverse, ne trouve que get_wellness_for_date)

Source Intervals.icu uniquement. Les outils du serveur retenu depuis #165 (hhopke/intervals-icu-mcp) portent le préfixe icu_ ; l'ancien serveur (eddmann/…) les exposait sans préfixe. Si seuls les noms sans préfixe existent, l'ancien serveur est encore installé : python3 scripts/coach_doctor.py --check intervals_mcp_pin, puis ./install.sh --source intervals et une nouvelle session (voir Mise à jour). Si les outils icu_* existent déjà mais que votre session ne les voit pas : la session a été ouverte avant la mise à jour, rouvrez-en une.

download_fit.py affiche INDISPONIBLE (Intervals.icu)

L'activité a été importée dans Intervals.icu depuis Strava : l'API Strava interdit sa redistribution, il n'existe aucun FIT à télécharger. Ce n'est pas une panne : la séance reste valide, sans les KPI fins (zones, GAP, VAM…). Connectez la montre, ou l'app qui l'exporte, directement à Intervals.icu pour les séances suivantes. HTTP 401/403 signale en revanche une clé API refusée : régénérez-la sur https://intervals.icu/settings (section Developer).

Santé Open Wearables : instance injoignable, 401, provider_mismatch, données périmées

Seulement si [health].source = "openwearables" (voir Open Wearables). Commencez par python3 scripts/coach_doctor.py --check openwearables (il appelle votre instance, sauf avec --no-probe-ow) : il dit laquelle de ces pannes vous concerne, sans jamais afficher la clé d'API. La synchronisation Garmin n'est jamais bloquée par une panne d'Open Wearables.

  • Instance injoignable : l'instance (docker compose up -d) ne tourne pas ou [health.openwearables].base_url est faux (c'est l'adresse de l'API, pas celle du portail). Corrigez-la en relançant ./install.sh --health-source openwearables --ow-url URL --ow-provider FABRICANT.
  • 401, clé refusée : la clé a été révoquée ou remplacée. Créez-en une nouvelle dans le portail d'OW puis ./install.sh --health-source openwearables --ow-url URL --ow-provider FABRICANT --ow-key-file FICHIER.
  • Fichier de clé en mode 644 (ou lisible par d'autres) : chmod 600 ~/.config/ai-running-coach/openwearables.key.
  • provider_mismatch : OW retient UNE source par date selon ses priorités et ce n'est pas votre fabricant. Dans le portail : Settings → Priorities, placez le fournisseur choisi en tête. Tant que ce n'est pas fait, la nuit n'est pas utilisée (une seule source de santé par jour).
  • Données périmées (stale) : la dernière synchronisation est plus ancienne que [health.openwearables].stale_after_h (36 h par défaut). Vérifiez dans le portail que le fournisseur est connecté (statut active, pas expired ni revoked), que le worker et le planificateur d'OW tournent, et reconnectez-le si besoin.
  • Fournisseur absent ou inconnu : provider vide ou faute de frappe ne repasse pas sur la source principale, il laisse la configuration incomplète — le diagnostic le signale.

Les autres scripts échouent

Les scripts analyze_gpx.py, compare_course.py et analyze_session_parts.py utilisent uniquement la stdlib Python — aucune dépendance externe n'est nécessaire.

Autres

Le script d'installation ne trouve pas Homebrew

En mode passerelle, le script affiche des instructions d'installation manuelle pour leanproxy-mcp. Installez-le manuellement puis relancez le script.

Problème non résolu ?

Ouvrez une issue avec :

  • Le système d'exploitation et sa version
  • La version de votre IDE
  • La sortie complète de ./install.sh
  • Les messages d'erreur exacts