fix(worker): strip trailing separator from the list repo in ConPTY launch args
The ConPTY host flattens LaunchSpec.Args into one Windows command line and quotes each token, so a list working dir stored as "C:\repo\" produced the token "C:\repo\" -- whose trailing backslash escapes its own closing quote. Everything after it collapsed into --add-dir's variadic list, including --append-system-prompt-file and the positional kickoff, so "Let Claude handle it" opened a session with no prompt at all and the CLI warned that brief.md is not a directory. Only user-supplied working dirs can carry a trailing separator; the session dirs the worker builds never do.
This commit is contained in:
@@ -618,6 +618,31 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
Assert.Equal(InteractiveLaunchSpecService.McpToolTimeoutMs, spec.Env["MCP_TOOL_TIMEOUT"]);
|
||||
}
|
||||
|
||||
// A directory argument that keeps its trailing separator escapes its own closing quote once the
|
||||
// ConPTY host flattens Args into a single Windows command line ("C:\repo\" -> \" is a literal
|
||||
// quote), so --add-dir's variadic list swallows every following argument -- including the
|
||||
// positional kickoff, leaving the session with no prompt at all.
|
||||
[Fact]
|
||||
public async Task BuildForMergeHelperAsync_WorkingDirWithTrailingSeparator_EmitsNoArgEndingInSeparator()
|
||||
{
|
||||
var repo = Path.Combine(_tempDir, "repoTrailingSep");
|
||||
Directory.CreateDirectory(repo);
|
||||
|
||||
var listId = await SeedListAsync(workingDir: repo + Path.DirectorySeparatorChar, name: "Trailing");
|
||||
var t1 = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(t1, listId, TaskStatus.WaitingForReview, title: "First task");
|
||||
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForMergeHelperAsync(new[] { t1 }, listId, CancellationToken.None);
|
||||
var sessionDir = TrackSessionDir(spec);
|
||||
|
||||
var args = spec.Args.ToList();
|
||||
var addIdx = args.IndexOf("--add-dir");
|
||||
var appendIdx = args.IndexOf("--append-system-prompt-file");
|
||||
Assert.Equal(new[] { sessionDir, repo }, args.GetRange(addIdx + 1, appendIdx - addIdx - 1));
|
||||
Assert.DoesNotContain(args, a => a.EndsWith('\\') || a.EndsWith('/'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForMergeHelperAsync_BriefNamesListRepoAndEveryTask()
|
||||
{
|
||||
@@ -826,6 +851,29 @@ public sealed class InteractiveLaunchSpecServiceTests : IDisposable
|
||||
Assert.Contains("working directory", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForMergeHelperHandoffAsync_WorkingDirWithTrailingSeparator_EmitsNoArgEndingInSeparator()
|
||||
{
|
||||
var repo = Path.Combine(_tempDir, "repoHandoffTrailingSep");
|
||||
Directory.CreateDirectory(repo);
|
||||
|
||||
var listId = await SeedListAsync(workingDir: repo + Path.DirectorySeparatorChar, name: "Trailing");
|
||||
var handlerTaskId = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(handlerTaskId, listId, TaskStatus.Idle, title: "List handler: Trailing");
|
||||
var survivor = Guid.NewGuid().ToString();
|
||||
await SeedTaskAsync(survivor, listId, TaskStatus.WaitingForReview, title: "Survivor");
|
||||
|
||||
var svc = BuildService();
|
||||
var spec = await svc.BuildForMergeHelperHandoffAsync(handlerTaskId, new[] { survivor }, CancellationToken.None);
|
||||
var sessionDir = TrackSessionDir(spec);
|
||||
|
||||
var args = spec.Args.ToList();
|
||||
var addIdx = args.IndexOf("--add-dir");
|
||||
var appendIdx = args.IndexOf("--append-system-prompt-file");
|
||||
Assert.Equal(new[] { sessionDir, repo }, args.GetRange(addIdx + 1, appendIdx - addIdx - 1));
|
||||
Assert.DoesNotContain(args, a => a.EndsWith('\\') || a.EndsWith('/'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildForMergeHelperHandoffAsync_ReusesHandlerTaskId_NoNewTaskCreated()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user