Skip to content

fix(daemon): let the daemon exit while a detached lease is outstanding - #90

Merged
V3RON merged 1 commit into
mainfrom
claude/pr-85-rebase-description-3s22wy
Sep 2, 2026
Merged

fix(daemon): let the daemon exit while a detached lease is outstanding#90
V3RON merged 1 commit into
mainfrom
claude/pr-85-rebase-description-3s22wy

Conversation

@V3RON

@V3RON V3RON commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Replaces #85, rebased directly onto main instead of the owned-device-roots stack — this defect predates that work and is unrelated to it.

The defect

simlock daemon stop returns promptly and the process stays alive. The operator is left with a stopped daemon still holding its socket, and whatever restarts it finds the address in use.

LeaseEngine.dispose — the hook DaemonServer#stop invokes — cancelled the quarantine coordinator's timers and not the expiry scheduler's:

/** Cancels the quarantine coordinator's armed retry timers on daemon shutdown. */
dispose(): void {
  this.#quarantine.dispose();
}

A lease's TTL is a real setTimeout, so an outstanding lease kept Node's event loop alive for as long as its deadline had left — fifteen minutes by default for a detached one. LeaseExpiryScheduler.dispose() existed and was never called from production code.

Held leases hid it: shutdown releases them, and releasing cancels the timer. Detached leases are deliberately left alone, since their liveness is the TTL rather than a connection, so they were the only ones left armed.

Why the fix is safe

Cancelling expires nothing early and loses nothing: ttlDeadline is persisted with the lease and re-armed by LeaseExpiryScheduler.restore on the next start. That is the same mechanism that lets a detached lease outlive a restart in the first place, which is the property that had to be preserved — the fix must not become "release everything on shutdown".

Not part of the owned-device-roots stack

It reproduces on main, with none of that stack applied. #85 carried it as the sixth of six stacked PRs and additionally picked up unrelated review fixes from #84 along the way, which drew a request to split this fix out and land it directly on main. This PR is that split: only LeaseEngine.dispose and its tests, cherry-picked from #85's 08abd5de53. The comment-only hunk in e2e/lease-environment-passthrough.test.ts is dropped here since that file doesn't exist on main yet — it's introduced by #83.

Verification

The unit test fails against the old code: removing this.#expiry.dispose() gives expected 1 to be +0, an armed timer surviving disposal. Coverage is in two places:

  • a unit test asserting no timer survives disposal and that the lease itself is untouched, since cancelling a timer must not expire a lease;
  • an e2e case in daemon lifecycle & recovery that leases detached, stops the daemon, and lets teardown's stray-process check be the assertion.

pnpm run check green: typecheck, typecheck:e2e, lint, format, 995 unit tests, e2e 35 passed / 1 expected fail / 9 skipped. fallow audit clean across 3 changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_015ia6AUpUzuF7aL3Z4CKUCx


Generated by Claude Code

`daemon stop` returned promptly and the process stayed. `LeaseEngine
.dispose` cancelled the quarantine coordinator's timers and not the
expiry scheduler's, and a lease's TTL is a real `setTimeout`, so an
outstanding lease kept Node's event loop alive for as long as its
deadline had left -- fifteen minutes, by default, for a detached one. The
operator sees a stopped daemon holding its socket, and whatever restarts
it finds the address in use.

Held leases hid it: shutdown releases them, and releasing cancels the
timer. Detached leases are deliberately left alone, since their liveness
is the TTL rather than a connection, so they were the only ones left
armed -- which is why nothing in the suite had tripped over it until an
e2e flow left one behind.

Cancelling expires nothing early and loses nothing. `ttlDeadline` is
persisted with the lease and re-armed by `LeaseExpiryScheduler.restore`
on the next start, which is the same mechanism that lets a detached lease
outlive a restart in the first place.

Predates the device-root work: it reproduces on main.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ia6AUpUzuF7aL3Z4CKUCx
@V3RON
V3RON merged commit b12b086 into main Sep 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants