claude-failover/WORK_IN_PROGRESS.md
Ubuntu 91091d7abf feat(symlinks): add shared-state symlink manager (A1)
Adds internal/symlinks package that encodes in code the convention
previously maintained by hand on the VM: every Claude account home
must expose `session-env`, `file-history` and `projects` as symlinks
to a single shared target, so account failover does not create
divergent state (duplicate JSONL transcripts, broken undo history).

- EnsureForAccount(home, required) creates missing links and target
  directories, refuses to auto-correct a divergent link (risks data
  loss), and errors when a regular file sits where the link belongs.
- ValidateAll(homes, required) aggregates errors across both accounts
  so the operator sees every problem at once rather than fixing one
  per restart cycle.
- RequiredShared exposes the production defaults so lifecycle and
  switcher (A2/A3) can depend on it directly.

9/9 unit tests green.

Part of Phase 1 Chantier A — Failover robuste.
2026-04-16 18:55:32 +00:00

4.1 KiB

Travaux en Cours - claude-failover

Dernière mise à jour

2026-04-16 19:00:00

Version Actuelle

0.3.5 (en cours de progression vers 0.4.0)

Demande Actuelle

Phase 1 / Chantier A — Failover robuste (spec dans ccl-platform/phases/phase1/A-failover.md). Rendre le failover compte1 ↔ compte2 déterministe en intégrant dans le code les fixes manuels (symlinks partagés), en ajoutant un registre UUID fiable, et en durcissant tmux send-keys.

Branche : feat/phase1-A-failover-robust.

Sous-tâches Chantier A

  • A1 — internal/symlinks/shared.go (+ tests) — v0.3.5
  • A2 — lifecycle/manager.go : ValidateAll au startup
  • A3 — switcher/account_switcher.go : EnsureForAccount post-flip
  • A4 — internal/registry/uuid_registry.go (+ tests)
  • A5 — internal/tmux/send.go avec retry exponentiel (+ tests)
  • A6 — Capture UUID 200 → 500 lignes
  • A7 — scripts/test-failover.sh dans ccl-platform + scripts associés

Étapes Complétées

  • v0.2.1 — Cooldown post-swap + log forensique (trigger_session, pattern, snippet)
  • v0.2.2 — Confirmation 2-polls pour les hits sans reset time
  • v0.2.3 — Veto 5xx (api_error / overloaded_error / internal server error) et retrait du pattern générique "rate limit" (remplacé par rate_limit_error)
  • Documentation : docs/architecture.md §2.2.1 "False-positive protection"
  • Tests unitaires exhaustifs (14 cas pour isQuotaExhausted dont les 3 veto 5xx)
  • Déploiement prod : /usr/local/bin/claude-failover + service redémarré
  • Push sur Forgejo origin/main (commits 7c5f838 et 62e98cb)

Prochaines Étapes

  • préserver les sessions dédiées lors d'un swap légitime — fait en v0.3.0 via saveDedicatedUUIDs + relaunchDedicatedSessions.
  • Optionnel : telegram alert quand SwapRequested est émis pour que l'opérateur soit au courant sans lire les logs. Le notifier.Telegram existe déjà — il suffit de câbler.
  • Optionnel : exposer /quota/status via internal/api avec les champs last_swap_at, suspected_hit_at, cooldown_remaining pour le dashboard.

Contexte Important

  • Symptôme en prod (2026-04-15) : daemon faisait des swaps compte1↔compte2 en boucle (intervalle descendant à 1 min), tuant les sessions interactives ccl-1 et ccl-2 en permanence. reset="" sur tous les events.
  • Cause racine identifiée via le log forensique v0.2.1 : les 500 d'Anthropic contenaient le texte "rate limit" dans leur payload. Snippet capturé : API Error: 500 {"type":"error","error":{"type":"api_error",...}}. Le monitor les confondait avec de vrais 429.
  • Config reactivate_cooldown: "5m" existait déjà dans config.yaml mais n'était consommée que par le dispatcher — pas par le monitor. v0.2.1 a branché le monitor dessus.
  • Comptes disponibles : compte1 (Claude Max), compte2 (Claude Team). Symlink actuel : ~/.claude → .claude-compte2.
  • Sessions tmux gérées : pool autonome ccl-auto-* (min=2, max=10) + dédiées ccl-1-conformvault, ccl-2-scanyze.

Fichiers Modifiés (cette série de fixes)

  • internal/quota/monitor.go — quotaPatterns, serverErrorPatterns, suspectedHitAt, cooldown
  • internal/quota/monitor_test.go — 14 sous-tests isQuotaExhausted + 3 tests poll
  • internal/state/state.go — LastSwapAt/From/To + RecordSwap + LastSwapInfo
  • internal/switcher/account_switcher.go — appel state.RecordSwap() après swap
  • docs/architecture.md — §2.2.1 False-positive protection
  • VERSION.md — changelog 0.2.1 → 0.2.3

Bugs Connus

  • Sessions dédiées tuées lors d'un swap légitime : comportement documenté et voulu (respawn sur le nouveau compte), mais coupe brutalement le travail interactif en cours. Voir Prochaines Étapes.

Historique des Demandes (Récentes)

Date Version Demande Status
2026-04-15 0.2.1 Casser le ping-pong + logs forensiques Terminé
2026-04-15 0.2.2 Confirmation 2-polls pour absorber les flashes Terminé
2026-04-15 0.2.3 Veto 5xx + patterns stricts (root cause) Terminé