This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+1 more
Summary
AI summaryFixed daemon wedging when dependency readiness flaps by serializing pause/resume and restoring subcontainer distinction.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Bugfix | Medium |
Dependency-gated daemon no longer wedges permanently after dependency readiness flaps. Dependency-gated daemon no longer wedges permanently after dependency readiness flaps. Source: llm_adapter@2026-07-16 Confidence: low |
— |
| Bugfix | Medium |
Dependency-driven pause/resume transitions are now serialized. Dependency-driven pause/resume transitions are now serialized. Source: llm_adapter@2026-07-16 Confidence: low |
— |
Full changelog
What's Changed
Fixed
- A dependency-gated daemon no longer wedges permanently after its dependency's readiness flaps. Since 2.0.0's hold/release refactor,
Daemon.term()unconditionally destroyed the daemon'sSubContainer, andHealthDaemoncallsterm()every time a dependency goes not-ready. When the dependency's health then recovered, the daemon'sstart()calledhold()on the already-destroyed subcontainer and threwcannot hold subcontainer …: already destroyed. Because thatstart()was un-awaited, it surfaced as an unhandled rejection and the daemon never recovered — the health check reported "not ready" / "daemon crashed" indefinitely, curable only by a full package restart. Any service with a dependency-gated daemon was exposed (e.g.c-lightning's and BTCPay's web UIs). The 2.0.0 refactor had removed thedestroySubcontainerflag that previously kept the subcontainer alive across a stop, collapsing "pause" and "teardown" into one destroyingterm(). This restores the distinction under the hold/release model:Daemongains a non-destroyingstop()(aborts the loop, terminates the process, releases the hold — but leaves theSubContainerintact so a laterstart()re-holds it), andHealthDaemon.changeRunning(false)now callsstop()for a dependency-driven pause.Daemon.term()(used byHealthDaemon.term()andDaemons.term()for genuine teardown) still releases the hold and destroys the subcontainer.HealthDaemon.changeRunning(true)additionally awaits and catchesstart(), so a start failure becomes a health failure rather than a silent unhandled-rejection wedge - Dependency-driven pause/resume transitions are now serialized.
HealthDaemon.updateStatus()runs fire-and-forget from dependency watchers, so a fast readiness flap (not-ready → ready before the pause'sstop()finished draining the process) could overlap the pause with the following resume. BecauseDaemon.start()is a no-op while the previous run loop is still winding down, the resume'sstart()could silently do nothing and leave the daemon stopped while the health session believed it was running — the same class of wedge, reachable via a fast flap.HealthDaemonnow applies pause/resume transitions on an in-order promise chain, so a resume always waits for the in-flight pause to complete before re-holding and restarting
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About StartOS
Browser-based, graphical Operating System (OS) that makes running a personal server as easy as running a personal computer.
Related context
Related tools
Earlier breaking changes
- vstart-sdk/v2.0.4 'setupMain' callback now accepts any DaemonBuildable rather than requiring a Daemons.
- vstart-sdk/v2.0.4 `Daemons.dynamic` now takes `effects` and returns a `DaemonReconciler` instead of replacing `main`.
- v0.4.0-beta.9 Previous backups incompatible with v0.4.0; create fresh backup after updating.
Beta — feedback welcome: [email protected]