Merge claudedo/81b054800dc4446598685fd04fefddb5

This commit is contained in:
mika kuns
2026-08-06 13:45:15 +02:00
3 changed files with 55 additions and 11 deletions
+29 -8
View File
@@ -325,7 +325,9 @@ public sealed class TaskStateService : ITaskStateService
// Unconditional status write — bypasses transition rules. Used by the UI's
// "set status freely" affordance; intentionally no guards (caller may strand
// the runner if used while a task is executing).
// the runner if used while a task is executing). It also bypasses chain/parent
// advancement — forcing a chain child or a WaitingForChildren parent's child to a
// terminal status here does not unblock its successor or re-check the parent.
public async Task<TransitionResult> ForceSetStatusAsync(string taskId, TaskStatus status, CancellationToken ct)
{
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
@@ -418,13 +420,32 @@ public sealed class TaskStateService : ITaskStateService
{
var resultText = "[stale] " + reason;
var now = DateTime.UtcNow;
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
return await ctx.Tasks
.Where(t => t.Status == TaskStatus.Running)
.ExecuteUpdateAsync(s => s
.SetProperty(t => t.Status, TaskStatus.Failed)
.SetProperty(t => t.FinishedAt, now)
.SetProperty(t => t.Result, resultText), ct);
List<string> recoveredIds;
int affected;
await using (var ctx = await _dbFactory.CreateDbContextAsync(ct))
{
recoveredIds = await ctx.Tasks
.Where(t => t.Status == TaskStatus.Running)
.Select(t => t.Id)
.ToListAsync(ct);
affected = await ctx.Tasks
.Where(t => t.Status == TaskStatus.Running)
.ExecuteUpdateAsync(s => s
.SetProperty(t => t.Status, TaskStatus.Failed)
.SetProperty(t => t.FinishedAt, now)
.SetProperty(t => t.Result, resultText), ct);
}
// A recovered task may have been a planning/improvement chain child or the last
// non-terminal child of a WaitingForChildren parent. The bulk flip above skips the
// usual terminal-transition side effects, so without this a crash mid-run would
// leave the chain successor blocked forever and the parent wedged in
// WaitingForChildren with nothing left to re-check it.
foreach (var taskId in recoveredIds)
await OnChildTerminalAsync(taskId, TaskStatus.Failed);
return affected;
}
// A subtask is "draft" only while its planning parent has an open (Active) session.