fix: Worktree get() matches wrong agent due to ambiguous endsWith lookup

The `get(id)` method on SimpleGitWorktreeManager used `path.endsWith(id)`
to find worktrees. Since all agents working on the same project create
worktrees with the same project name suffix (e.g., "codewalk-district"),
cleanup for one agent could match and delete another agent's worktree.

Fix: match on `basename(worktreesDir)/id` so each manager's lookups are
scoped to its own worktree base directory.
This commit is contained in:
Lukas May
2026-03-06 14:52:28 +01:00
parent 7a4d0d2582
commit 4a7105eb8f
2 changed files with 60 additions and 1 deletions

View File

@@ -453,6 +453,58 @@ describe('SimpleGitWorktreeManager', () => {
});
});
// ==========================================================================
// Cross-Agent Isolation
// ==========================================================================
describe('cross-agent isolation', () => {
it('get() only matches worktrees in its own worktreesDir', async () => {
// Simulate two agents with separate worktree base dirs but same repo
const agentADir = path.join(repoPath, 'workdirs', 'agent-a');
const agentBDir = path.join(repoPath, 'workdirs', 'agent-b');
await mkdir(agentADir, { recursive: true });
await mkdir(agentBDir, { recursive: true });
const managerA = new SimpleGitWorktreeManager(repoPath, undefined, agentADir);
const managerB = new SimpleGitWorktreeManager(repoPath, undefined, agentBDir);
// Both create worktrees with the same id (project name)
await managerA.create('my-project', 'agent/agent-a');
await managerB.create('my-project', 'agent/agent-b');
// Each manager should only see its own worktree
const wtA = await managerA.get('my-project');
const wtB = await managerB.get('my-project');
expect(wtA).not.toBeNull();
expect(wtB).not.toBeNull();
expect(wtA!.path).toContain('agent-a');
expect(wtB!.path).toContain('agent-b');
expect(wtA!.path).not.toBe(wtB!.path);
});
it('remove() only removes worktrees in its own worktreesDir', async () => {
const agentADir = path.join(repoPath, 'workdirs', 'agent-a');
const agentBDir = path.join(repoPath, 'workdirs', 'agent-b');
await mkdir(agentADir, { recursive: true });
await mkdir(agentBDir, { recursive: true });
const managerA = new SimpleGitWorktreeManager(repoPath, undefined, agentADir);
const managerB = new SimpleGitWorktreeManager(repoPath, undefined, agentBDir);
await managerA.create('my-project', 'agent/agent-a');
await managerB.create('my-project', 'agent/agent-b');
// Remove agent A's worktree
await managerA.remove('my-project');
// Agent B's worktree should still exist
const wtB = await managerB.get('my-project');
expect(wtB).not.toBeNull();
expect(wtB!.path).toContain('agent-b');
});
});
// ==========================================================================
// Edge Cases
// ==========================================================================