From 7873e60095d75a9ccfdcb5930de19f5ab1c63b53 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 4 Jun 2026 15:39:24 +0200 Subject: [PATCH] feat(state): advance WaitingForChildren parent to review when children terminal Co-Authored-By: Claude Sonnet 4.6 --- src/ClaudeDo.Worker/State/TaskStateService.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/ClaudeDo.Worker/State/TaskStateService.cs b/src/ClaudeDo.Worker/State/TaskStateService.cs index ce248a3..6a55a88 100644 --- a/src/ClaudeDo.Worker/State/TaskStateService.cs +++ b/src/ClaudeDo.Worker/State/TaskStateService.cs @@ -392,5 +392,56 @@ public sealed class TaskStateService : ITaskStateService { _logger.LogWarning(ex, "TryCompleteParent failed for {ParentId}", parentId); } + + try + { + await TryAdvanceImprovementParentAsync(parentId); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "TryAdvanceImprovementParent failed for {ParentId}", parentId); + } + } + + // Improvement parents sit in WaitingForChildren while their suggested children run. + // Once every child is terminal (Done/Failed/Cancelled) the parent surfaces for review; + // a failed or cancelled child does not wedge the parent — it is flagged on the result. + private async Task TryAdvanceImprovementParentAsync(string parentId) + { + string? parentResult; + List childStatuses; + await using (var ctx = await _dbFactory.CreateDbContextAsync(CancellationToken.None)) + { + var parent = await ctx.Tasks.AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == parentId, CancellationToken.None); + if (parent is null || parent.Status != TaskStatus.WaitingForChildren) return; + parentResult = parent.Result; + childStatuses = await ctx.Tasks + .Where(t => t.ParentTaskId == parentId) + .Select(t => t.Status) + .ToListAsync(CancellationToken.None); + } + if (childStatuses.Count == 0) return; + + bool allTerminal = childStatuses.All(s => + s == TaskStatus.Done || s == TaskStatus.Failed || s == TaskStatus.Cancelled); + if (!allTerminal) return; + + int failed = childStatuses.Count(s => s == TaskStatus.Failed); + int cancelled = childStatuses.Count(s => s == TaskStatus.Cancelled); + var newResult = parentResult; + if (failed + cancelled > 0) + { + var note = $"⚠ Children: {failed} failed, {cancelled} cancelled."; + newResult = string.IsNullOrWhiteSpace(parentResult) ? note : $"{parentResult}\n\n{note}"; + } + + await using var writeCtx = await _dbFactory.CreateDbContextAsync(CancellationToken.None); + await writeCtx.Tasks + .Where(t => t.Id == parentId && t.Status == TaskStatus.WaitingForChildren) + .ExecuteUpdateAsync(s => s + .SetProperty(t => t.Status, TaskStatus.WaitingForReview) + .SetProperty(t => t.Result, newResult), CancellationToken.None); + await _broadcaster.TaskUpdated(parentId); } }