// Le chemin du CLI claude est mémorisé pour la vie du process (le `which` coûte un fork par spawn). // Régression : un daemon qui tourne des jours voyait ce chemin devenir invalide (bascule de version // nvm/asdf, réinstallation du CLI) et continuait de spawner un fichier disparu. Le PTY mourait sans // produire un seul octet, ce qui donnait un terminal vide et muet. Le cache est donc revalidé. import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; let dir: string; /** Réponse courante du faux `which` : on la fait varier comme le ferait un changement de version. */ let onPath: string; function makeBin(name: string): string { const path = join(dir, name); writeFileSync(path, '#!/bin/sh\nexit 0\n'); chmodSync(path, 0o755); return path; } beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'arb-claude-bin-')); vi.resetModules(); vi.doMock('node:child_process', () => ({ execFileSync: () => `${onPath}\n` })); }); afterEach(() => { vi.doUnmock('node:child_process'); rmSync(dir, { recursive: true, force: true }); }); describe('resolveClaudeBin · cache revalidé', () => { it('le chemin caché disparu est re-résolu au lieu d’être servi tel quel', async () => { const first = makeBin('claude-v1'); onPath = first; const { resolveClaudeBin } = await import('../src/core/claude-launcher.js'); expect(resolveClaudeBin()).toBe(first); // le CLI est réinstallé ailleurs : l'ancien chemin n'existe plus rmSync(first); const second = makeBin('claude-v2'); onPath = second; expect(resolveClaudeBin()).toBe(second); }); it('tant que le chemin caché existe, aucun `which` supplémentaire n’est fait', async () => { const bin = makeBin('claude-stable'); onPath = bin; const calls: number[] = []; vi.doMock('node:child_process', () => ({ execFileSync: () => { calls.push(1); return `${onPath}\n`; }, })); vi.resetModules(); const { resolveClaudeBin } = await import('../src/core/claude-launcher.js'); expect(resolveClaudeBin()).toBe(bin); expect(resolveClaudeBin()).toBe(bin); expect(resolveClaudeBin()).toBe(bin); expect(calls).toHaveLength(1); }); });