fix(worker): propagate unit-merge failures instead of reporting success

A child merge that came back blocked/verify_failed/untracked_collision during a
parent/children unit merge used to vanish: DrainAsync only logged it server-side,
PlanningMergeAborted carried no reason, and ApproveReview/review_task always
reported StatusMerged for a task with children regardless of the real outcome,
so a failed unit merge left the parent stuck with no visible error.

- PlanningMergeOrchestrator.StartAsync/ContinueAsync/DrainAsync now return a
  PlanningMergeResult(Status, Reason) instead of void, and PlanningMergeAborted
  carries that reason to the UI.
- WorkerHub.ApproveReview and ExternalMcpService.ReviewTask's approve branch
  propagate the real status/reason for a parent with children instead of
  hardcoding "merged" (or masking a non-conflict failure as "conflict").
- StartAsync now requires the parent to already be WaitingForReview for
  improvement parents too, not just planning ones, so a stale caller can no
  longer trigger a partial child merge.
- HasActiveMerge now also covers the window between the last child merging and
  FinalizeParentDoneAsync completing, closing a gap where a concurrent Cancel
  could race the parent's own approve-to-Done transition.
- IslandsShellViewModel.OnPlanningMergeAborted flashes the reason via
  FlashFooterError instead of only clearing the external-merge banner.
This commit is contained in:
mika kuns
2026-08-20 15:02:17 +02:00
parent 4cf08f8159
commit f205843020
14 changed files with 352 additions and 54 deletions
+2 -2
View File
@@ -64,8 +64,8 @@ public sealed class HubBroadcaster : IPrimeBroadcaster, IRefineBroadcaster
string planningTaskId, string subtaskId, IReadOnlyList<string> files, bool externallyDriven) =>
_hub.Clients.All.SendAsync("PlanningMergeConflict", planningTaskId, subtaskId, files, externallyDriven);
public Task PlanningMergeAborted(string planningTaskId) =>
_hub.Clients.All.SendAsync("PlanningMergeAborted", planningTaskId);
public Task PlanningMergeAborted(string planningTaskId, string? reason) =>
_hub.Clients.All.SendAsync("PlanningMergeAborted", planningTaskId, reason);
public Task PlanningCompleted(string planningTaskId) =>
_hub.Clients.All.SendAsync("PlanningCompleted", planningTaskId);
+4 -2
View File
@@ -744,8 +744,10 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
if (hasChildren)
{
await _planningMergeOrchestrator.StartAsync(taskId, targetBranch ?? "", CancellationToken.None);
return new MergeResultDto(TaskMergeService.StatusMerged, Array.Empty<string>(), null);
var unitResult = await _planningMergeOrchestrator.StartAsync(taskId, targetBranch ?? "", CancellationToken.None);
if (unitResult.Status == TaskMergeService.StatusBlocked)
throw new HubException(unitResult.Reason ?? "approve failed");
return new MergeResultDto(unitResult.Status, Array.Empty<string>(), unitResult.Reason);
}
var r = await _mergeService.ApproveAndMergeAsync(taskId, targetBranch ?? "", CancellationToken.None);