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
Warm pool logs "record could not be read: database is not open" when the daemon stops right after starting #985
A worker that stops right after its start (here http-start-failed) logs a warm-pool read failure because state.db is already closed:
{"module":"daemon.server","message":"Daemon started", ...} ts ...451907
{"module":"daemon.server","message":"Daemon stopping","fields":{"reason":"http-start-failed"}} ts ...451907
{"level":"warn","module":"daemon.warm-pool","message":"the warm pool's record could not be read","fields":{"error":"Error: database is not open"}} ts ...451909
{"module":"daemon.connection-host","message":"Released daemon socket", ...}
The warning comes from src/core/warm-pool/converger.ts:233 (#learnedKinds). A pass still runs after the database is closed, so the log shows a record failure that did not happen. Seen with the fake driver, HTTP port held, listManaged latency 3 s.
Defect. The warning is real, but the issue names the wrong caller: the read comes from the cleanup reaper's run, not from a warm pool pass. That run starts on daemon.started and is still in flight when the stop closes state.db.
src/daemon/main.test.ts › "startDaemon stop with a cleanup run in flight (#985)" › "logs no warning or error once a stop lands while the cleanup run daemon.started began is still in flight". Branch bug/985-repro.
pnpm exec vitest run --project unit src/daemon/main.test.ts -t "985"
AssertionError: expected [ { …(3) } ] to deeply equal []
+ { "fields": { "error": "Error: database is not open" },
+ "message": "the warm pool's record could not be read",
+ "module": "daemon.warm-pool" }
The test holds the run's diskFree read, stops the daemon, then lets the read finish.
Root cause
src/daemon/server.ts:639 calls reaper.dispose(), then settle and dispose close state.db (src/daemon/main.ts:725). CleanupReaper.dispose (src/core/reaper.ts:82) only unsubscribes and cancels its timer. A run already past await diskFree (reaper.ts:204) keeps going and calls targetedDevices (reaper.ts:212). That calls WarmPool.targeted() (converger.ts:299), and its #learnedKinds read fails on the closed database (converger.ts:230). warmPool.drain() waits only for passes and flights, not for targeted() callers. The warning at converger.ts:233 is where the symptom shows, not the cause.
Simplest fix
Add CleanupReaper.settle(), which awaits #running. In #stop, await it after reaper.dispose() and before options.settle. This follows architecture rule 12: a graceful stop waits for work already in flight.
Alternatives rejected
Return early when #disposed is set after each await in the reaper: an action already running still races the teardown.
Skip the warning in #learnedKinds once the pool is disposed: hides the read and leaves the run acting after stop.
Risk
A run that is executing a destroy now holds the stop open until that destroy ends.
Without the fix, a run past #view executes cleanup actions after drivers are disposed. Check that the fix covers this too.
Maintainer accepted the triage report's proposed fix: add CleanupReaper.settle() and await it in the stop, after reaper.dispose() and before anything closes. A stop that lands during a device destroy waits for it. The regression test should prove that no cleanup work runs after the stop (for example no driver or state.db call), not only that a log line is absent (testing rule 8).
Activity
V3RON commentedon Oct 25, 2026
Reproduction
Defect. The warning is real, but the issue names the wrong caller: the read comes from the cleanup reaper's run, not from a warm pool pass. That run starts on
daemon.startedand is still in flight when the stop closesstate.db.src/daemon/main.test.ts› "startDaemon stop with a cleanup run in flight (#985)" › "logs no warning or error once a stop lands while the cleanup run daemon.started began is still in flight". Branchbug/985-repro.The test holds the run's
diskFreeread, stops the daemon, then lets the read finish.Root cause
src/daemon/server.ts:639callsreaper.dispose(), thensettleanddisposeclosestate.db(src/daemon/main.ts:725).CleanupReaper.dispose(src/core/reaper.ts:82) only unsubscribes and cancels its timer. A run already pastawait diskFree(reaper.ts:204) keeps going and callstargetedDevices(reaper.ts:212). That callsWarmPool.targeted()(converger.ts:299), and its#learnedKindsread fails on the closed database (converger.ts:230).warmPool.drain()waits only for passes and flights, not fortargeted()callers. The warning atconverger.ts:233is where the symptom shows, not the cause.Simplest fix
Add
CleanupReaper.settle(), which awaits#running. In#stop, await it afterreaper.dispose()and beforeoptions.settle. This follows architecture rule 12: a graceful stop waits for work already in flight.Alternatives rejected
#disposedis set after each await in the reaper: an action already running still races the teardown.#learnedKindsonce the pool is disposed: hides the read and leaves the run acting after stop.Risk
#viewexecutes cleanup actions after drivers are disposed. Check that the fix covers this too.Written by an agent.
V3RON commentedon Oct 25, 2026
Decision
Maintainer accepted the triage report's proposed fix: add
CleanupReaper.settle()and await it in the stop, afterreaper.dispose()and before anything closes. A stop that lands during a device destroy waits for it. The regression test should prove that no cleanup work runs after the stop (for example no driver orstate.dbcall), not only that a log line is absent (testing rule 8).Written by an agent.
fix(daemon): a stop waits for the cleanup run in flight before closin…
test(daemon): the #985 stop test releases the held read by event-loop…
test(daemon): a stop ends the device deletion a cleanup run has in fl…
fix(daemon): a stop waits for dry-run and rule-scoped cleanup runs in…
V3RON commentedon Oct 25, 2026
Delivery run
Merged: #999 (#985)
Parked: none
Waiting: none
Decided: #985 — should the regression test also prove a driver action ends before driver dispose? → yes, added (maintainer Decision + testing rule 8)
Written by an agent.