Phase 1 / A3 — EnsureSharedSymlinks après flipSymlink dans switcher #2

Open
olivier wants to merge 2 commits from feat/phase1-A3-switcher-symlinks into feat/phase1-A-failover-robust
Owner

Résumé

Phase 1 / Chantier A3 — dernière pièce de A. Après chaque flip du symlink ~/.claude vers le compte cible, le switcher appelle maintenant symlinks.EnsureForAccount(target.Home, RequiredShared) pour garantir que les 3 liens partagés (session-env, file-history, projects) pointent vers les targets canoniques.

Base : feat/phase1-A-failover-robust (PR A à merger d'abord).

Commit

  • 8eaf0bb feat(switcher): ensure shared symlinks on target home after flip (A3)

Design

  • Erreur EnsureForAccount loggée en WARN mais n'abort PAS le swap (best-effort post-flip). Raison : abort laisserait le symlink principal flippé mais SetActiveAccount jamais appelé → état incohérent. Mieux vaut logger et continuer.
  • Override test-only AccountSwitcher.sharedSymlinks (default = symlinks.RequiredShared) pour scoper les tests dans t.TempDir().

Tests

  • TestFlipReconcilesSharedSymlinksOnTargetHome : target home vide → 3 liens créés avec bons targets après flip
  • TestFlipEnsureSymlinksFailureDoesNotAbortSwap : lien divergent planté → erreur refusing to auto-correct, swap complète quand même, ActiveAccount == "compte2"
  • Migrés TestDedicatedRelaunchAfterSwap + TestKillAndRecreatePoolSessions vers tempdir pour éviter fuites filesystem
  • go test ./... PASS + -race clean + go vet clean

Voir VERSION.md 0.3.7.

## Résumé Phase 1 / Chantier A3 — dernière pièce de A. Après chaque flip du symlink `~/.claude` vers le compte cible, le switcher appelle maintenant `symlinks.EnsureForAccount(target.Home, RequiredShared)` pour garantir que les 3 liens partagés (`session-env`, `file-history`, `projects`) pointent vers les targets canoniques. **Base** : `feat/phase1-A-failover-robust` (PR A à merger d'abord). ## Commit - `8eaf0bb` feat(switcher): ensure shared symlinks on target home after flip (A3) ## Design - Erreur `EnsureForAccount` loggée en WARN mais n'abort PAS le swap (best-effort post-flip). Raison : abort laisserait le symlink principal flippé mais `SetActiveAccount` jamais appelé → état incohérent. Mieux vaut logger et continuer. - Override test-only `AccountSwitcher.sharedSymlinks` (default = `symlinks.RequiredShared`) pour scoper les tests dans `t.TempDir()`. ## Tests - `TestFlipReconcilesSharedSymlinksOnTargetHome` : target home vide → 3 liens créés avec bons targets après flip - `TestFlipEnsureSymlinksFailureDoesNotAbortSwap` : lien divergent planté → erreur refusing to auto-correct, swap complète quand même, ActiveAccount == "compte2" - Migrés `TestDedicatedRelaunchAfterSwap` + `TestKillAndRecreatePoolSessions` vers tempdir pour éviter fuites filesystem - ✅ `go test ./...` PASS + `-race` clean + `go vet` clean Voir `VERSION.md` 0.3.7.
olivier added 1 commit 2026-04-16 19:39:22 +00:00
Wire symlinks.EnsureForAccount into executeSwitch, called immediately
after the ~/.claude flip. Guarantees the three shared-state links
(session-env, file-history, projects) exist on the target account home
even for freshly-provisioned accounts, preventing silent transcript
duplication and undo-history divergence on first resume.

Best-effort: errors are logged as WARN but never abort the swap. If we
returned here the daemon would be left inconsistent (symlink flipped,
SetActiveAccount never called). Operator sees the warning in logs and
resolves divergent links manually.

Tests:
- TestFlipReconcilesSharedSymlinksOnTargetHome: empty target home gets
  all three links pointing at canonical targets after the flip.
- TestFlipEnsureSymlinksFailureDoesNotAbortSwap: a planted divergent
  link triggers the symlinks-package error; the swap completes anyway
  and the active account is updated.

Hermetic: added AccountSwitcher.sharedSymlinks override so tests scope
the reconcile inside t.TempDir() and never touch
/home/ubuntu/.claude-*-shared. Existing tests migrated to this pattern
and hardcoded /tmp/claude-*-xxxx paths replaced with tmpdirs.

Phase 1 / Chantier A — task A3.
olivier added 1 commit 2026-04-16 19:57:56 +00:00
Bug #1 (CRITIQUE) — A3 flip+ensure inconsistency
- Before: EnsureForAccount failure after flip was WARN-only, SetActiveAccount
  still fired → daemon declared target active while shared symlinks were
  absent/divergent → transcripts silently duplicated, resume broken.
- After: ensure failure triggers rollback flip to previous account home;
  if rollback succeeds → explicit error, ActiveAccount stays on previous.
  If rollback ALSO fails → sticky partialSwap flag + ErrPartialSwap; all
  further swaps refused until operator intervention (daemon restart).
- New public IsPartialSwap() for watchdog / health-check integration.

Bug #10 (MOYENNE) — requiredShared contract never exercised
- All existing tests override a.sharedSymlinks with tmpdir-scoped lists,
  so symlinks.RequiredShared itself was never tested. A rename or drop
  would pass every test but silently break prod failover.
- TestRequiredSharedIsCoherent asserts (no filesystem): 3 entries with
  the exact required names, absolute targets, and a single shared parent
  directory (invariant EnsureForAccount depends on).

Tests:
- go test ./... PASS
- go test -race ./... PASS (no data race)
- 2 new switcher tests: TestFlipEnsureFailureTriggersRollback,
  TestFlipEnsureAndRollbackFailure
- 1 new symlinks test: TestRequiredSharedIsCoherent
- 1 obsolete test replaced: TestFlipEnsureSymlinksFailureDoesNotAbortSwap
  (encoded the old buggy best-effort behaviour)
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/phase1-A3-switcher-symlinks:feat/phase1-A3-switcher-symlinks
git checkout feat/phase1-A3-switcher-symlinks

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git checkout feat/phase1-A-failover-robust
git merge --no-ff feat/phase1-A3-switcher-symlinks
git checkout feat/phase1-A3-switcher-symlinks
git rebase feat/phase1-A-failover-robust
git checkout feat/phase1-A-failover-robust
git merge --ff-only feat/phase1-A3-switcher-symlinks
git checkout feat/phase1-A3-switcher-symlinks
git rebase feat/phase1-A-failover-robust
git checkout feat/phase1-A-failover-robust
git merge --no-ff feat/phase1-A3-switcher-symlinks
git checkout feat/phase1-A-failover-robust
git merge --squash feat/phase1-A3-switcher-symlinks
git checkout feat/phase1-A-failover-robust
git merge --ff-only feat/phase1-A3-switcher-symlinks
git checkout feat/phase1-A-failover-robust
git merge feat/phase1-A3-switcher-symlinks
git push origin feat/phase1-A-failover-robust
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: olivier/claude-failover#2
No description provided.