Stop publishing captures for a task the worker no longer holds

publishCaptures iterated every session the worker had, with no reference to
whether it still owned the lease. Run 10's task was blocked and unleased for
twenty-six minutes while this called the coordinator every five seconds and
logged "409 Conflict: lease not owned" each time.

The cost was not only the traffic. The single last_error slot stayed pinned to
that dead task, so run 11's own expiry reason was never visible, which is F18
turning a small leak into a blind spot.

A capture is lease-scoped. holdsLease names that invariant so the guard is
testable, rather than inline where a stub backend makes the test vacuous.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
2026-08-28 17:17:52 +04:00
parent 936fa74cf0
commit 44ff35aeb3
2 changed files with 37 additions and 0 deletions
+21
View File
@@ -1102,3 +1102,24 @@ func TestStageExcludeSurvivesTheIgnoredMarker(t *testing.T) {
t.Fatalf("staged %q, want only a.txt", strings.TrimSpace(staged))
}
}
// A capture is lease-scoped. Without that guard, publishCaptures called the
// coordinator for every session the worker had ever held: run 10's task was
// blocked and unleased for twenty-six minutes while this logged "409 Conflict:
// lease not owned" every five seconds, pinning the single last_error slot to a
// dead task.
func TestHoldsLeaseGatesWorkOnATaskTheWorkerLost(t *testing.T) {
w := &worker{leases: map[string]lease{
"held": {Until: time.Now().Add(time.Minute)},
"expired": {Until: time.Now().Add(-time.Minute)},
}}
if !w.holdsLease("held") {
t.Error("an unexpired lease is not held")
}
if w.holdsLease("expired") {
t.Error("an expired lease is still held")
}
if w.holdsLease("never-leased") {
t.Error("a task this worker never leased is held")
}
}