fix(worker,ui): block cancelling a task while its unit merge is draining
TaskStateService.CancelAsync allowed cancelling a WaitingForReview task even while PlanningMergeOrchestrator was mid-drain on it: ApproveReview awaits the whole multi-subtask merge synchronously, so a concurrent CancelReview (UI or MCP) could flip the parent to Cancelled while the orchestrator kept merging children onto the target branch, then FinalizeParentDoneAsync would find the parent no longer WaitingForReview and give up - leaving the merged diffs stranded with no rollback. CancelAsync now rejects with a clear reason when HasActiveMerge(taskId) is true. TaskStateService can't take PlanningMergeOrchestrator as a direct constructor dependency (circular back to ITaskStateService), so it takes a lazily-resolved Func<IActiveMergeState> instead, mirroring the existing Func<ITaskStateService> cycle-break already used for PlanningChainCoordinator. UI polish: DetailsIslandViewModel.IsMergeDraining gates CancelReviewCommand's CanExecute (same shape as WorktreesOverviewModalViewModel.IsMerging), and the command's catch now raises ErrorReported instead of swallowing the rejection silently.
This commit is contained in:
@@ -20,7 +20,8 @@ public static class TaskStateServiceBuilder
|
||||
CountingQueueWaker Waker,
|
||||
RunCancellationRegistry RunCancels);
|
||||
|
||||
public static Built Build(IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
||||
public static Built Build(
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory, Func<IActiveMergeState>? mergeState = null)
|
||||
{
|
||||
var hub = new CapturingHubContext();
|
||||
var broadcaster = new HubBroadcaster(hub);
|
||||
@@ -35,12 +36,19 @@ public static class TaskStateServiceBuilder
|
||||
waker,
|
||||
chain,
|
||||
runCancels,
|
||||
mergeState ?? (() => NoActiveMergeState.Instance),
|
||||
NullLogger<TaskStateService>.Instance);
|
||||
|
||||
return new Built(state, chain, hub, () => waker.Count, waker, runCancels);
|
||||
}
|
||||
}
|
||||
|
||||
file sealed class NoActiveMergeState : IActiveMergeState
|
||||
{
|
||||
public static readonly NoActiveMergeState Instance = new();
|
||||
public bool HasActiveMerge(string taskId) => false;
|
||||
}
|
||||
|
||||
public sealed class CountingQueueWaker : IQueueWaker
|
||||
{
|
||||
private int _count;
|
||||
|
||||
Reference in New Issue
Block a user