fix(worker): harden CLI injection, stuck-Running, chain wedge, and Fail guard
1. ArgumentList (fix injection): ClaudeArgsBuilder.Build() now returns IReadOnlyList<string>; ClaudeProcess populates ProcessStartInfo.ArgumentList instead of Arguments, so values like system prompts are never shell-split. DailyPrepPrompt, RefinePrompt, and WeekReportService migrated similarly. All IClaudeProcess fakes updated. 2. ContinueAsync exception guard: wrap RunOnceAsync in try/catch matching the RunAsync pattern so an unexpected exception never leaves the task stuck in Running status. 3. Planning chain cascade: OnChildFinishedAsync now calls CancelAsync on the immediate blocked successor when a child fails or is cancelled, triggering a recursive cascade that clears the entire remaining chain instead of leaving it wedged. 4. FailAsync guard: restrict valid source states to Running and Queued; WaitingForReview -> Failed is now rejected, preventing an invalid transition that could corrupt the review workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,7 +47,7 @@ public sealed class QueueServiceTests : IDisposable
|
||||
private QueueWaker _waker = null!;
|
||||
|
||||
private (QueueService service, FakeClaudeProcess fakeProcess) CreateService(
|
||||
Func<string, string, string, Func<string, Task>, CancellationToken, Task<RunResult>>? handler = null)
|
||||
Func<string, string, IReadOnlyList<string>, Func<string, Task>, CancellationToken, Task<RunResult>>? handler = null)
|
||||
{
|
||||
var fake = new FakeClaudeProcess(handler);
|
||||
var broadcaster = new HubBroadcaster(new FakeHubContext());
|
||||
@@ -118,7 +118,7 @@ public sealed class QueueServiceTests : IDisposable
|
||||
{
|
||||
var (listId, _) = await SeedListWithAgentTag();
|
||||
|
||||
string? capturedArgs = null;
|
||||
IReadOnlyList<string>? capturedArgs = null;
|
||||
string? capturedPrompt = null;
|
||||
var done = new TaskCompletionSource();
|
||||
|
||||
@@ -157,7 +157,9 @@ public sealed class QueueServiceTests : IDisposable
|
||||
_waker.Wake();
|
||||
await done.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
Assert.Contains("--resume sess-1", capturedArgs);
|
||||
Assert.NotNull(capturedArgs);
|
||||
Assert.Contains("--resume", capturedArgs);
|
||||
Assert.Contains("sess-1", capturedArgs);
|
||||
Assert.Equal("fix the bug", capturedPrompt);
|
||||
|
||||
// Feedback is cleared after the run reaches a successful terminal state (post-run),
|
||||
@@ -346,19 +348,19 @@ public sealed class QueueServiceTests : IDisposable
|
||||
|
||||
internal sealed class FakeClaudeProcess : IClaudeProcess
|
||||
{
|
||||
private readonly Func<string, string, string, Func<string, Task>, CancellationToken, Task<RunResult>> _handler;
|
||||
private readonly Func<string, string, IReadOnlyList<string>, Func<string, Task>, CancellationToken, Task<RunResult>> _handler;
|
||||
private int _callCount;
|
||||
|
||||
public int CallCount => _callCount;
|
||||
|
||||
public FakeClaudeProcess(
|
||||
Func<string, string, string, Func<string, Task>, CancellationToken, Task<RunResult>>? handler = null)
|
||||
Func<string, string, IReadOnlyList<string>, Func<string, Task>, CancellationToken, Task<RunResult>>? handler = null)
|
||||
{
|
||||
_handler = handler ?? ((_, _, _, _, _) =>
|
||||
Task.FromResult(new RunResult { ExitCode = 0, ResultMarkdown = "ok" }));
|
||||
}
|
||||
|
||||
public async Task<RunResult> RunAsync(string arguments, string prompt, string workingDirectory,
|
||||
public async Task<RunResult> RunAsync(IReadOnlyList<string> arguments, string prompt, string workingDirectory,
|
||||
Func<string, Task> onStdoutLine, CancellationToken ct)
|
||||
{
|
||||
Interlocked.Increment(ref _callCount);
|
||||
|
||||
Reference in New Issue
Block a user