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:
@@ -211,19 +211,32 @@ public sealed class TaskRunner
|
||||
await _state.StartRunningAsync(taskId, now, ct);
|
||||
await _broadcaster.TaskStarted(slot, taskId, now);
|
||||
|
||||
var nextRunNumber = lastRun.RunNumber + 1;
|
||||
var result = await RunOnceAsync(taskId, task.Title, slot, runDir, resolvedConfig, nextRunNumber, false, followUpPrompt, ct);
|
||||
|
||||
if (result.IsSuccess)
|
||||
try
|
||||
{
|
||||
await HandleSuccess(task, list, slot, wtCtx, result, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MarkFailed(taskId, task.Title, slot, result.ErrorMarkdown, result.TurnCount);
|
||||
}
|
||||
var nextRunNumber = lastRun.RunNumber + 1;
|
||||
var result = await RunOnceAsync(taskId, task.Title, slot, runDir, resolvedConfig, nextRunNumber, false, followUpPrompt, ct);
|
||||
|
||||
await _broadcaster.TaskUpdated(taskId);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
await HandleSuccess(task, list, slot, wtCtx, result, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MarkFailed(taskId, task.Title, slot, result.ErrorMarkdown, result.TurnCount);
|
||||
}
|
||||
|
||||
await _broadcaster.TaskUpdated(taskId);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Task {TaskId} was cancelled during continue", taskId);
|
||||
await MarkFailed(taskId, task.Title, slot, "Task cancelled.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unhandled exception continuing task {TaskId}", taskId);
|
||||
await MarkFailed(taskId, task.Title, slot, $"Unhandled error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct RunDirResult(string? RunDir, WorktreeContext? WtCtx, string? FailureReason);
|
||||
|
||||
Reference in New Issue
Block a user