fix(worker): Usage-Limit-Auto-Continue verbrennt nicht mehr alle Kandidaten außer dem ersten
OverrideSlotService hält genau einen Slot, und ein Usage-Limit stoppt naturgemäß alle laufenden Tasks gleichzeitig — "mehr Kandidaten als Kapazität" ist also der Normalfall. Der Coordinator stempelte UsageLimitAutoContinuedAt aber VOR dem ContinueTask (der Marker ist die Dedupe-Guard), sodass Kandidat 2..n den Marker bekamen, deren ContinueTask mit "override slot busy" in den catch flog und sie danach dauerhaft aus GetUsageLimitAutoContinueCandidatesAsync ausgeschlossen waren — ein Continue, der nie lief. - Vorab-Check auf CurrentSlot: bei belegtem Slot bricht der Tick ab, statt die restlichen Kandidaten zu verbrennen. - Neuer TaskRepository.ReleaseUsageLimitAutoContinueClaimAsync gibt den Claim zurück, wenn ContinueTask wirft; danach return, der 30s-Backstop holt den Rest im nächsten Tick. - Regressionstest mit zwei Kandidaten, deterministisch über eine TaskCompletionSource im FakeClaudeProcess (StartInSlot setzt _slot synchron unter dem Lock, bevor die Arbeit startet).
This commit is contained in:
@@ -235,6 +235,16 @@ public sealed class TaskRepository
|
||||
return affected > 0;
|
||||
}
|
||||
|
||||
/// Undoes a claim whose ContinueTask never actually started (the single override slot was
|
||||
/// busy, the run was rejected). Without this the task keeps a marker for a run that never
|
||||
/// happened and the candidate query excludes it forever.
|
||||
public async Task ReleaseUsageLimitAutoContinueClaimAsync(string taskId, CancellationToken ct = default)
|
||||
{
|
||||
await _context.Tasks
|
||||
.Where(t => t.Id == taskId)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(t => t.UsageLimitAutoContinuedAt, (DateTime?)null), ct);
|
||||
}
|
||||
|
||||
internal async Task<int> FlipAllRunningToFailedAsync(string reason, CancellationToken ct = default)
|
||||
{
|
||||
var resultText = "[stale] " + reason;
|
||||
|
||||
Reference in New Issue
Block a user