refactor: merge TaskRunner failure handlers and reuse NullIfBlank

Unify the near-identical HandleFailure/MarkFailed into a single MarkFailed that
always persists the failed state and never throws, and replace the inline
null-if-blank checks in ListMcpTools with the existing extension.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-05-30 15:51:14 +02:00
parent ce9fadc0b5
commit 1856943925
2 changed files with 14 additions and 23 deletions

View File

@@ -31,8 +31,8 @@ public sealed class ListMcpTools
{ {
Id = Guid.NewGuid().ToString(), Id = Guid.NewGuid().ToString(),
Name = name, Name = name,
WorkingDir = string.IsNullOrWhiteSpace(workingDir) ? null : workingDir, WorkingDir = workingDir.NullIfBlank(),
DefaultCommitType = string.IsNullOrWhiteSpace(commitType) ? CommitTypeRegistry.DefaultType : commitType, DefaultCommitType = commitType.NullIfBlank() ?? CommitTypeRegistry.DefaultType,
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
}; };
await _lists.AddAsync(entity, cancellationToken); await _lists.AddAsync(entity, cancellationToken);
@@ -51,9 +51,9 @@ public sealed class ListMcpTools
throw new InvalidOperationException("name cannot be blank."); throw new InvalidOperationException("name cannot be blank.");
if (name is not null) entity.Name = name; if (name is not null) entity.Name = name;
if (workingDir is not null) if (workingDir is not null)
entity.WorkingDir = string.IsNullOrWhiteSpace(workingDir) ? null : workingDir; entity.WorkingDir = workingDir.NullIfBlank();
if (commitType is not null) if (commitType is not null)
entity.DefaultCommitType = string.IsNullOrWhiteSpace(commitType) ? CommitTypeRegistry.DefaultType : commitType; entity.DefaultCommitType = commitType.NullIfBlank() ?? CommitTypeRegistry.DefaultType;
await _lists.UpdateAsync(entity, cancellationToken); await _lists.UpdateAsync(entity, cancellationToken);
await _broadcaster.ListUpdated(listId); await _broadcaster.ListUpdated(listId);

View File

@@ -129,12 +129,12 @@ public sealed class TaskRunner
} }
else else
{ {
await HandleFailure(task.Id, task.Title, slot, retryResult); await MarkFailed(task.Id, task.Title, slot, retryResult.ErrorMarkdown, retryResult.TurnCount);
} }
} }
else else
{ {
await HandleFailure(task.Id, task.Title, slot, result); await MarkFailed(task.Id, task.Title, slot, result.ErrorMarkdown, result.TurnCount);
} }
} }
@@ -210,7 +210,7 @@ public sealed class TaskRunner
} }
else else
{ {
await HandleFailure(taskId, task.Title, slot, result); await MarkFailed(taskId, task.Title, slot, result.ErrorMarkdown, result.TurnCount);
} }
await _broadcaster.TaskUpdated(taskId); await _broadcaster.TaskUpdated(taskId);
@@ -331,26 +331,17 @@ public sealed class TaskRunner
task.Id, result.TurnCount, result.TokensIn, result.TokensOut); task.Id, result.TurnCount, result.TokensIn, result.TokensOut);
} }
private async Task HandleFailure(string taskId, string taskTitle, string slot, RunResult result) private async Task MarkFailed(string taskId, string taskTitle, string slot, string? error, int turnCount = 0)
{
// Intentionally does not accept a CancellationToken: this is the
// terminal write for a failed task and must always be persisted.
var finishedAt = DateTime.UtcNow;
await _state.FailAsync(taskId, finishedAt, result.ErrorMarkdown, CancellationToken.None);
await _broadcaster.WorkerLog($"Finished \"{taskTitle}\" (failed)", WorkerLogLevel.Error, DateTime.UtcNow);
await _broadcaster.TaskFinished(slot, taskId, "failed", finishedAt);
_logger.LogWarning("Task {TaskId} failed (turns={Turns}): {Error}", taskId, result.TurnCount, result.ErrorMarkdown);
}
private async Task MarkFailed(string taskId, string taskTitle, string slot, string error)
{ {
// Terminal write for a failed task: never cancel (the status must always
// be persisted) and never throw (a logging failure must not mask the error).
try try
{ {
var now = DateTime.UtcNow; var finishedAt = DateTime.UtcNow;
// Terminal write — never cancel. await _state.FailAsync(taskId, finishedAt, error, CancellationToken.None);
await _state.FailAsync(taskId, now, error, CancellationToken.None);
await _broadcaster.WorkerLog($"Finished \"{taskTitle}\" (failed)", WorkerLogLevel.Error, DateTime.UtcNow); await _broadcaster.WorkerLog($"Finished \"{taskTitle}\" (failed)", WorkerLogLevel.Error, DateTime.UtcNow);
await _broadcaster.TaskFinished(slot, taskId, "failed", now); await _broadcaster.TaskFinished(slot, taskId, "failed", finishedAt);
_logger.LogWarning("Task {TaskId} failed (turns={Turns}): {Error}", taskId, turnCount, error);
} }
catch (Exception ex) catch (Exception ex)
{ {