fix(worker): add failureReason field to batch_get_tasks

The tool description claimed Failed tasks carry failureReason, but the field
was missing from BatchTaskDetailDto and rejected when requested in `fields`.
Add failureReason to the DTO, KnownDetailFields, and BuildDetail so list
handlers and other callers can read failure reasons without per-task get_task
calls. Tested: 31 BatchMcpToolsTests pass, including two new cases for
failureReason retrieval on Failed and Idle tasks.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-09-02 12:20:02 +02:00
co-authored by Claude Haiku 4.5
parent 56bc1651a8
commit 912201770f
2 changed files with 37 additions and 0 deletions
+3
View File
@@ -45,6 +45,7 @@ public sealed record BatchTaskDetailDto(
int? SortOrder = null, int? SortOrder = null,
int? RoadblockCount = null, int? RoadblockCount = null,
string? RoadblockText = null, string? RoadblockText = null,
string? FailureReason = null,
bool DescriptionTruncated = false, bool DescriptionTruncated = false,
int? DescriptionFullLength = null, int? DescriptionFullLength = null,
bool ResultTruncated = false, bool ResultTruncated = false,
@@ -80,6 +81,7 @@ public sealed class BatchMcpTools
{ {
"listId", "title", "description", "status", "result", "createdBy", "createdAt", "listId", "title", "description", "status", "result", "createdBy", "createdAt",
"startedAt", "finishedAt", "isMyDay", "sortOrder", "roadblockCount", "roadblockText", "startedAt", "finishedAt", "isMyDay", "sortOrder", "roadblockCount", "roadblockText",
"failureReason",
}; };
private readonly ExternalMcpService _svc; private readonly ExternalMcpService _svc;
@@ -187,6 +189,7 @@ public sealed class BatchMcpTools
SortOrder: Want("sortOrder") ? t.SortOrder : null, SortOrder: Want("sortOrder") ? t.SortOrder : null,
RoadblockCount: Want("roadblockCount") ? t.RoadblockCount : null, RoadblockCount: Want("roadblockCount") ? t.RoadblockCount : null,
RoadblockText: Want("roadblockText") ? ExtractRoadblockText(t.Result) : null, RoadblockText: Want("roadblockText") ? ExtractRoadblockText(t.Result) : null,
FailureReason: Want("failureReason") ? t.FailureReason : null,
DescriptionTruncated: descTruncated, DescriptionTruncated: descTruncated,
DescriptionFullLength: descFullLength, DescriptionFullLength: descFullLength,
ResultTruncated: resultTruncated, ResultTruncated: resultTruncated,
@@ -623,4 +623,38 @@ public sealed class BatchMcpToolsTests : IDisposable
Assert.Equal(2, progress.Reports.Count); Assert.Equal(2, progress.Reports.Count);
Assert.Equal(2, progress.Reports[1].Total); Assert.Equal(2, progress.Reports[1].Total);
} }
[Fact]
public async Task BatchGetTasks_FailedTaskWithFailureReason_ReturnsReasonWhenRequested()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId, "failed", TaskStatus.Failed);
task.FailureReason = "max-turns-exceeded";
await _tasks.UpdateAsync(task);
var sut = BuildSut();
var results = await sut.BatchGetTasks(
new[] { task.Id }, includeDescription: true, fields: new[] { "failureReason" },
cancellationToken: CancellationToken.None);
var found = results.Single(r => r.Id == task.Id).TaskFull!;
Assert.Equal("max-turns-exceeded", found.FailureReason);
Assert.Null(found.Description);
Assert.Null(found.Status);
}
[Fact]
public async Task BatchGetTasks_IdleTaskWithoutFailureReason_ReturnsNullWhenRequested()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId, "idle", TaskStatus.Idle);
var sut = BuildSut();
var results = await sut.BatchGetTasks(
new[] { task.Id }, includeDescription: true, fields: new[] { "failureReason" },
cancellationToken: CancellationToken.None);
var found = results.Single(r => r.Id == task.Id).TaskFull!;
Assert.Null(found.FailureReason);
}
} }