Merge task branch for: fix(worker): Abort-Pfad für unterbrochenen Unit-Merge nach Worker-Restart

This commit is contained in:
mika kuns
2026-06-09 23:46:37 +02:00
2 changed files with 85 additions and 3 deletions

View File

@@ -85,7 +85,8 @@ public sealed class PlanningMergeOrchestrator
}
if (await _git.IsMidMergeAsync(workingDir, ct))
throw new InvalidOperationException("repo is mid-merge");
throw new InvalidOperationException(
"repo is mid-merge; use AbortPlanningMerge to reset the repository, then Approve again");
if (await _git.HasChangesAsync(workingDir, ct))
throw new InvalidOperationException("working tree has uncommitted changes");
@@ -110,7 +111,8 @@ public sealed class PlanningMergeOrchestrator
public async Task ContinueAsync(string planningTaskId, CancellationToken ct)
{
if (!_states.TryGetValue(planningTaskId, out var state) || state.CurrentSubtaskId is null)
throw new InvalidOperationException("no in-progress merge to continue");
throw new InvalidOperationException(
"no in-progress merge to continue; if the worker was restarted during a conflict, use AbortPlanningMerge to reset the repository");
var current = state.CurrentSubtaskId;
var result = await _merge.ContinueMergeAsync(current, ct);
@@ -140,13 +142,40 @@ public sealed class PlanningMergeOrchestrator
public async Task AbortAsync(string planningTaskId, CancellationToken ct)
{
if (!_states.TryGetValue(planningTaskId, out var state) || state.CurrentSubtaskId is null)
throw new InvalidOperationException("no in-progress merge to abort");
{
// No in-memory state — worker may have been restarted while a conflict was paused.
// Check whether the list repo is still mid-merge and abort it directly.
await AbortStatelessAsync(planningTaskId, ct);
return;
}
await _merge.AbortMergeAsync(state.CurrentSubtaskId, ct);
_states.TryRemove(planningTaskId, out _);
await _broadcaster.PlanningMergeAborted(planningTaskId);
}
private async Task AbortStatelessAsync(string planningTaskId, CancellationToken ct)
{
string? workingDir;
await using (var ctx = _dbFactory.CreateDbContext())
{
workingDir = await ctx.Tasks
.Where(t => t.Id == planningTaskId)
.Select(t => t.List.WorkingDir)
.FirstOrDefaultAsync(ct);
}
if (string.IsNullOrWhiteSpace(workingDir) || !await _git.IsMidMergeAsync(workingDir, ct))
throw new InvalidOperationException("no in-progress merge to abort");
await _git.MergeAbortAsync(workingDir, ct);
_logger.LogInformation(
"Stateless abort of mid-merge for planning task {ParentId} (post-restart recovery)",
planningTaskId);
await _broadcaster.PlanningMergeAborted(planningTaskId);
// Parent remains WaitingForReview — Approve will restart the unit merge from scratch.
}
private async Task DrainAsync(string planningTaskId, CancellationToken ct)
{
if (!_states.TryGetValue(planningTaskId, out var state)) return;