You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A waiter that times out while its lease is being written gets a timeout, and the lease is kept #402
On the daemon's grant path, the waiter's timeout timer stays armed while Registry.createLease writes the lease (src/leasing/lease-acquisition-coordinator.ts:669-674). If it fires in that window, the caller gets QueueTimeoutError but the lease exists: the device stays leased to nobody until the lease expires. #grant ignores the result of queue.resolve. This happened on main before #397 too; since #397, a repeat under the same idempotency key at least gets that lease back.
Confirmed on origin/main (1b5537da). On bug/402-repro (0d9ee5e0), lease-acquisition-coordinator.test.ts: "delivers the grant to a queued waiter
whose queue timeout falls due while its lease is being written".
pnpm exec vitest run --project unit src/leasing/lease-acquisition-coordinator.test.ts \
-t "delivers the grant to a queued waiter"
- "callerError": undefined,
+ "callerError": "QueueTimeoutError",
- "outcome": "fulfilled",
+ "outcome": "rejected",
The body is right that a repeat under the same key gets that lease back. Wider damage:
that lease belongs to a caller told it timed out, and blocks its next one.
Defect. lease-acquisition-coordinator.ts:874 grants a ready device without
marking the waiter processing, so it is still queued while #grant awaits the
write (:915). WaitQueue#armTimeout (wait-queue.ts:377) spares only a waiter
no longer queued, so the timer rejects this one mid-write. The grant is dropped: queue.resolve (:931) returns false, unread. Other paths mark first (:722, :887).
Simplest fix
Mark processing before the write, and re-queue a failed write as #grantPhysical
does (:762-779) — four lines in #decide:
if(!this.options.queue.markProcessing(waiter))returnundefined;try{awaitthis.#grant(waiter,plan.device,plan.kind);}catch(error: unknown){this.#logFailure("granting a ready device failed",waiter,plan.device,"grant",error);this.#defer(waiter);}
A waiter timed out mid-attempt keeps its lease and sees the grant — the queue's own
rule for that state (wait-queue.ts:339), not new policy. src/leasing green with
it: 590 passed.
Alternatives rejected
End the lease the write made: a lease ends only by release or TTL, and the device is
reclaimed after a grant was promised.
Release on a false from queue.resolve: the caller still times out over a device it
could have used.
Cancel this timer before the write: two places arm a deadline wait-queue.ts:332 says
is armed once.
Risk
waitingDemand (:628) now shows this waiter in flight during the write.
Done — Reproduced on origin/main (1b5537da) as a failing unit test on bug/402-repro (0d9ee5e0, one file, no PR): "delivers the grant to a queued waiter
whose queue timeout falls due while its lease is being written". Fails on a named
assertion; pnpm exec vitest run --project unit src/leasing gives 1 failed, 589
passed. Report posted above.
Not done — the fix, and turning the repro into the closing PR's regression test
(rule 9).
Findings — the outcome is not an open decision: wait-queue.ts:339 says a timeout
firing mid-attempt must not reject, and three paths already mark processing before one
starts. Only grant-ready does not. The proposed fix's catch is load-bearing — with markProcessing alone, a createLease that throws leaves the waiter pending, untimered
and not-cancellable.
Blocked on — nothing. Maintainer decides bug:ready.
Activity
V3RON commentedon Oct 16, 2026
Reproduction
Confirmed on
origin/main(1b5537da). Onbug/402-repro(0d9ee5e0),lease-acquisition-coordinator.test.ts: "delivers the grant to a queued waiterwhose queue timeout falls due while its lease is being written".
The body is right that a repeat under the same key gets that lease back. Wider damage:
that lease belongs to a caller told it timed out, and blocks its next one.
Root cause
Defect.
lease-acquisition-coordinator.ts:874grants a ready device withoutmarking the waiter
processing, so it is stillqueuedwhile#grantawaits thewrite (
:915).WaitQueue#armTimeout(wait-queue.ts:377) spares only a waiterno longer
queued, so the timer rejects this one mid-write. The grant is dropped:queue.resolve(:931) returnsfalse, unread. Other paths mark first (:722,:887).Simplest fix
Mark processing before the write, and re-queue a failed write as
#grantPhysicaldoes (
:762-779) — four lines in#decide:A waiter timed out mid-attempt keeps its lease and sees the grant — the queue's own
rule for that state (
wait-queue.ts:339), not new policy.src/leasinggreen withit: 590 passed.
Alternatives rejected
reclaimed after a grant was promised.
falsefromqueue.resolve: the caller still times out over a device itcould have used.
wait-queue.ts:332saysis armed once.
Risk
waitingDemand(:628) now shows this waiter in flight during the write.markProcessingwithout thecatchwedges the waiter — pending, no timer,not-cancellable; thecatchavoids the A lease request whose grant commit fails is left open forever, with no timer and no device work #647 shape.#wakeQueueafter#defer, or a failing grant spins.Side findings
Written by an agent.
V3RON commentedon Oct 16, 2026
Handoff
Done — Reproduced on
origin/main(1b5537da) as a failing unit test onbug/402-repro(0d9ee5e0, one file, no PR): "delivers the grant to a queued waiterwhose queue timeout falls due while its lease is being written". Fails on a named
assertion;
pnpm exec vitest run --project unit src/leasinggives 1 failed, 589passed. Report posted above.
Not done — the fix, and turning the repro into the closing PR's regression test
(rule 9).
Findings — the outcome is not an open decision:
wait-queue.ts:339says a timeoutfiring mid-attempt must not reject, and three paths already mark
processingbefore onestarts. Only
grant-readydoes not. The proposed fix'scatchis load-bearing — withmarkProcessingalone, acreateLeasethat throws leaves the waiter pending, untimeredand
not-cancellable.Blocked on — nothing. Maintainer decides
bug:ready.Written by an agent.