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 lease renew right after a gateway restart answers UNKNOWN_LEASE and the client drops the lease #983
After a gateway restart (including a self-update), a lease.renew that arrives before the lease's worker has reported answers UNKNOWN_LEASE. The gateway looks every id up only in its lease index, which is empty until each worker reports (src/gateway/lease-index.ts:14-20, src/gateway/fleet-coordinator.ts:1410-1418). CLI and MCP renewal treat UNKNOWN_LEASE as final (src/lease-policy/index.ts:187-189) and drop the lease.
The device stays held on the worker until its TTL. Once the index is rebuilt, the requester's next lease request is refused with REQUESTER_ALREADY_LEASED, naming the orphan.
What you expected
ADR 0005 §16 and §30: clients "simply resume renewing" after a gateway restart. A renew in the rebuild window gets a retryable error (WORKER_UNREACHABLE), and the lease survives.
How to reproduce
Gateway with one worker; an agent leases a device through the gateway.
Restart the gateway (or let it self-update).
Renew the lease within roughly 15–40 s, before the worker's first report: UNKNOWN_LEASE, and the client drops the lease.
Environment
main at 7a2f0b3. Found by reading code in the #412 review, not by a user report.
Logs and evidence
From the #412 review, finding R1. Proposed fix there: answer WORKER_UNREACHABLE for an id the index does not hold while the fleet is rebuilding. The window ends when every known worker has reported. ADR 0005 decision 5 says known worker ids are persisted, but only drained ids are (src/gateway/drain-store.ts:20). The fallback is a fixed 60 s window. The same window also fixes R3, R5 and R8.
Confirmed for generated ids (<workerId>.<workerLeaseId>). Branch bug/983-repro:
src/gateway/dispatcher.test.ts › "right after a gateway restart, renewing a generated lease id answers WORKER_UNREACHABLE until its worker reports, then renews the lease"
pnpm exec vitest run --project unit src/gateway/dispatcher.test.ts -t "right after a gateway restart"
AssertionError: before the worker reconnects: expected DispatchError: Unknown lease: wrk_1.lease… to match object { code: 'WORKER_UNREACHABLE' }
- "code": "WORKER_UNREACHABLE",
+ "code": "UNKNOWN_LEASE",
Cases: worker not reconnected; reconnected, leases not reported; reported (renew reaches the worker).
Root cause
Defect, for generated ids. #requireEntry (src/gateway/fleet-coordinator.ts:1410) looks the id up only in the in-memory index and throws UNKNOWN_LEASE on a miss. The index holds a worker's leases only after #onViewsChanged (:1458) sees its first running report. ADR 0005 §16 (still binding for generated ids, per ADR 0020) and §30 say clients just keep renewing. Caller-chosen ids are a Decision: ADR 0020 says a miss answers UNKNOWN_LEASE at once. Changing that needs a superseding ADR.
Simplest fix
In #requireEntry, on a miss for an id containing . (chosen ids cannot, LEASE_ID_PATTERN): if that worker has not reported since start (absent from #lastReconciledLeases) and the gateway started under 60 s ago, throw WORKER_UNREACHABLE with { workerId }. Renew, release and exec all get it.
Alternatives rejected
Route by splitting the id (§16): forwards before the owner is known, skipping §27a.
60 s window for every id: breaks ADR 0020's bare-id answer.
Persist known worker ids: more state, still needs a timeout.
Risk
dispatcher.test.ts "…UNKNOWN_LEASE for renew/release/exec against an id it never issued" uses wrk_ghost.lease_1 on a fresh gateway: move its clock past the window.
A lease that expired during the outage is retried until its deadline instead of dropped at once.
Update the Known gap "A lease survives a gateway restart…" in src/gateway/AGENTS.md and the comment at src/gateway/lease-index.ts:14-20.
Activity
V3RON commentedon Oct 25, 2026
Reproduction
Confirmed for generated ids (
<workerId>.<workerLeaseId>). Branchbug/983-repro:src/gateway/dispatcher.test.ts› "right after a gateway restart, renewing a generated lease id answers WORKER_UNREACHABLE until its worker reports, then renews the lease"Cases: worker not reconnected; reconnected, leases not reported; reported (renew reaches the worker).
Root cause
Defect, for generated ids.
#requireEntry(src/gateway/fleet-coordinator.ts:1410) looks the id up only in the in-memory index and throwsUNKNOWN_LEASEon a miss. The index holds a worker's leases only after#onViewsChanged(:1458) sees its firstrunningreport. ADR 0005 §16 (still binding for generated ids, per ADR 0020) and §30 say clients just keep renewing. Caller-chosen ids are a Decision: ADR 0020 says a miss answersUNKNOWN_LEASEat once. Changing that needs a superseding ADR.Simplest fix
In
#requireEntry, on a miss for an id containing.(chosen ids cannot,LEASE_ID_PATTERN): if that worker has not reported since start (absent from#lastReconciledLeases) and the gateway started under 60 s ago, throwWORKER_UNREACHABLEwith{ workerId }. Renew, release and exec all get it.Alternatives rejected
Risk
dispatcher.test.ts"…UNKNOWN_LEASE for renew/release/exec against an id it never issued" useswrk_ghost.lease_1on a fresh gateway: move its clock past the window.src/gateway/AGENTS.mdand the comment atsrc/gateway/lease-index.ts:14-20.Written by an agent.
V3RON commentedon Oct 25, 2026
Maintainer: held at
bug:triagefor now; no delivery yet.Written by an agent.