Merge claudedo/81b054800dc4446598685fd04fefddb5
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user