fix(worker): stop sibling worktrees from landing inside the repo tree
Path.GetDirectoryName returned workingDir itself instead of its parent whenever list.WorkingDir ended in a directory separator, placing .claudedo-worktrees inside the target repo's own working tree. Add WorktreeRootResolver to normalize the trailing separator before deriving the sibling root and to guard that the resulting worktree path never lands inside workingDir, used by both WorktreeManager and PlanningSessionManager. Add a startup sweep (LegacyWorktreeFolderRecovery) that warns when a list's working dir already contains a leftover .claudedo-worktrees folder from before the fix.
This commit is contained in:
@@ -123,6 +123,30 @@ public sealed class PlanningSessionManagerTests : IDisposable
|
||||
Assert.NotNull(loaded.PlanningSessionToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_TrailingSeparatorOnWorkingDir_PlacesWorktreeOutsideRepo()
|
||||
{
|
||||
var listId = Guid.NewGuid().ToString();
|
||||
var wd = Path.Combine(Path.GetTempPath(), $"cd_wd_{Guid.NewGuid():N}");
|
||||
GitRepoFixture.InitRepoWithInitialCommit(wd);
|
||||
await _lists.AddAsync(new ListEntity
|
||||
{
|
||||
Id = listId,
|
||||
Name = "Test",
|
||||
WorkingDir = wd + Path.DirectorySeparatorChar,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
});
|
||||
var parent = await SeedManualTaskAsync(listId);
|
||||
|
||||
var ctx = await _sut.StartAsync(parent.Id, CancellationToken.None);
|
||||
|
||||
var wdFull = Path.GetFullPath(wd);
|
||||
Assert.False(
|
||||
ctx.WorktreePath.StartsWith(wdFull + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase),
|
||||
$"worktree path {ctx.WorktreePath} should not be nested inside the repo dir {wdFull}");
|
||||
Assert.True(Directory.Exists(ctx.WorktreePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_TaskNotManual_Throws()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user