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:
mika kuns
2026-06-09 10:05:40 +02:00
parent 763732a9b3
commit 33bdff8a6e
20 changed files with 331 additions and 99 deletions

View File

@@ -11,9 +11,12 @@ public sealed class RefinePromptTests
{
var args = RefinePrompt.BuildArgs(20, canReadRepo: true);
Assert.Contains("--permission-mode acceptEdits", args);
Assert.Contains("--permission-mode", args);
Assert.Contains("acceptEdits", args);
Assert.Contains("mcp__claudedo__add_subtask", args);
Assert.Contains(" Read Grep Glob", args);
Assert.Contains("Read", args);
Assert.Contains("Grep", args);
Assert.Contains("Glob", args);
}
[Fact]

View File

@@ -105,7 +105,7 @@ internal sealed class RecordingClaudeProcess : IClaudeProcess
public RecordingClaudeProcess(bool success) => _success = success;
public Task<RunResult> RunAsync(string arguments, string prompt, string workingDirectory,
public Task<RunResult> RunAsync(IReadOnlyList<string> arguments, string prompt, string workingDirectory,
Func<string, Task> onStdoutLine, CancellationToken ct)
{
Interlocked.Increment(ref _callCount);