fix(worker): Stuck-Running-Fenster in Continue- und Stop-Pfad schließen

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.
This commit is contained in:
mika kuns
2026-08-20 15:00:14 +02:00
parent 4cf08f8159
commit 389c9126c8
6 changed files with 266 additions and 49 deletions
@@ -9,10 +9,15 @@ public sealed class FakeSessionSkillSeeder : ISessionSkillSeeder
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;
}
}