diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b9249..25c98ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ## Changelog -### v0.67.4 — gate: startup recreate also recovers exited/restarting drive-backed apps (2026-06-15) +### v0.67.5 — gate: startup recreate waits for stack scan + handles exited apps (2026-06-15) + +Adds a bounded wait for the stack scan (GetStacks is empty at NewServer time, so the recreate found no +apps) before the one-time boot-stale recreate, and recovers exited/restarting/unhealthy drive-backed +apps (not only recently-started). The deterministic guest-reboot convergence. Refines v0.67.3's `recreateBootStaleApps`: recreate a present drive-backed app when it is boot-stale (recently started) OR currently `exited`/`restarting`/`unhealthy` (came up wrong on the empty bind and diff --git a/controller/internal/web/intermediary.go b/controller/internal/web/intermediary.go index ae723b5..7a09613 100644 --- a/controller/internal/web/intermediary.go +++ b/controller/internal/web/intermediary.go @@ -203,6 +203,14 @@ func (s *Server) ReconcileDriveGates() { // startup recreate (see recreateBootStaleApps) to converge a guest reboot deterministically, then the // periodic gate. func (s *Server) driveGateLoop() { + // Wait for the stack scan to complete (GetStacks empty at NewServer time) and the agent to come up, + // so the one-time boot-stale recreate sees the real deployed apps + drive state. Bounded poll. + for i := 0; i < 30; i++ { + if s.stackMgr != nil && len(s.stackMgr.GetStacks()) > 0 { + break + } + time.Sleep(time.Second) + } s.recreateBootStaleApps() s.ReconcileDriveGates() t := time.NewTicker(30 * time.Second)