What happened: Every graceful shutdown panics FATAL: panic: pebble: closed at the tail end (after an otherwise-clean stop), exiting the process non-zero.
Root cause: server/start.go startInProcess registers two deferred cleanups that both call app.Close() — startCmtNode's cleanupFn (after tmNode.Stop()) and startApp's appCleanupFn. baseapp.(*BaseApp).Close() closes app.db unconditionally, so the second call re-closes the already-closed DB. PebbleDB panics pebble: closed on a second Close(); other backends (e.g. MemDB/GoLevelDB) silently tolerate it, which is why it's easy to miss.
Affected: main and release/v0.54.x (baseapp.go Close + server/start.go are identical there).
Impact: non-zero exit on every shutdown; noise that masks genuine Close() errors and pollutes crash monitoring. Cosmetic for the DB itself (the first close is clean) but real for ops.
Fix: make BaseApp.Close() idempotent (clear app.db / app.snapshotManager after closing). PR + regression test attached.
What happened: Every graceful shutdown panics FATAL: panic: pebble: closed at the tail end (after an otherwise-clean stop), exiting the process non-zero.
Root cause: server/start.go startInProcess registers two deferred cleanups that both call app.Close() — startCmtNode's cleanupFn (after tmNode.Stop()) and startApp's appCleanupFn. baseapp.(*BaseApp).Close() closes app.db unconditionally, so the second call re-closes the already-closed DB. PebbleDB panics pebble: closed on a second Close(); other backends (e.g. MemDB/GoLevelDB) silently tolerate it, which is why it's easy to miss.
Affected: main and release/v0.54.x (baseapp.go Close + server/start.go are identical there).
Impact: non-zero exit on every shutdown; noise that masks genuine Close() errors and pollutes crash monitoring. Cosmetic for the DB itself (the first close is clean) but real for ops.
Fix: make BaseApp.Close() idempotent (clear app.db / app.snapshotManager after closing). PR + regression test attached.