TaskRunner.ContinueAsync: Claim, SeedAsync und SetupMcpConfigAsync liefen vor dem try/catch (anders als RunAsync) - warf einer davon nach dem Running-Claim, propagierte die Exception ungefangen bis zu OverrideSlotService.RunContinueInSlotAsync, das nur loggt. Task blieb Running. Fix: derselbe Aufbau wie RunAsync (Claim+Seed+MCP-Setup im try, MarkFailed im catch, mcpToken/mcpConfigPath vor dem try auf null). SetupMcpConfigAsync bekommt zusätzlich einen onTokenRegistered-Callback, damit die äußere mcpToken-Variable den Token sofort nach dem Register sieht - sonst hätte ein Fehler zwischen Register und Rückgabe (z.B. File.WriteAllTextAsync) den Token in der TaskRunTokenRegistry geleakt (betrifft denselben Aufruf in RunAsync mit, daher dort ebenfalls verdrahtet - RunAsync-Struktur selbst unverändert). QueueService.RunInSlotAsync: der Stop-Button (CancelTask) cancelt die Slot-CTS direkt ohne TaskStateService-Schreibzugriff. Traf das die Pre-Dispatch-DB-Reads, loggte der OCE-Catch nur und die vom Picker bereits auf Running geclaimte Task blieb dort für immer hängen. Fix: Status im Catch neu lesen und nur wenn er noch Running ist über TaskStateService.CancelAsync auf Cancelled setzen - ein Ursprung, der bereits selbst einen Terminalstatus geschrieben hat (z.B. CancelReview), wird nicht überschrieben. Kommentar korrigiert. TDD: neue Tests in ContinueAsyncExceptionTests (Seed-/MCP-Setup-Fehler nach Claim -> Failed, kein Token-Leak) und QueueServiceSlotFailureTests (Stop während Pre-Dispatch -> Cancelled statt Running; ein bereits terminal geschriebener Status wird nicht gestompt) vorher rot, jetzt grün. Worker.Tests: 1213/1213 grün, Worker baut in Release.
24 lines
909 B
C#
24 lines
909 B
C#
using System.Collections.Concurrent;
|
|
using ClaudeDo.Worker.Skills;
|
|
|
|
namespace ClaudeDo.Worker.Tests.Infrastructure;
|
|
|
|
public sealed class FakeSessionSkillSeeder : ISessionSkillSeeder
|
|
{
|
|
public int CallCount;
|
|
public readonly record struct Call(string WorkingDir, IReadOnlyList<string> SkillNames, bool IsWorktree);
|
|
public readonly ConcurrentQueue<Call> Calls = new();
|
|
|
|
// Lets a test simulate a seeding failure (e.g. a broken skill install) to verify callers
|
|
// don't leave a task stuck Running when SeedAsync throws.
|
|
public Exception? ThrowOnSeed;
|
|
|
|
public Task SeedAsync(string workingDir, IReadOnlyList<string> skillNames, bool isWorktree, CancellationToken ct)
|
|
{
|
|
Interlocked.Increment(ref CallCount);
|
|
Calls.Enqueue(new Call(workingDir, skillNames, isWorktree));
|
|
if (ThrowOnSeed is not null) throw ThrowOnSeed;
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|