Retention sweeper nil-panics and crash-loops the engine on LVM(-fallback) pools (auto_delete.go:455)

Summary

With the new 4.2 retention feature enabled (retention.unusedSnapshotMinutes > 0 or unusedBranchMinutes > 0), the auto-deletion sweeper panics with a nil-pointer dereference and crash-loops the whole engine on every sweep tick if any pool in the pool manager is backed by the LVM manager. Found during 4.2 pre-GA testing on master @ 7a6f6470 (2026-07-13). Unlike F4/F5, this is in the new 4.2 code path (!1161 (merged)) — recommend fixing before GA.

panic: runtime error: invalid memory address or nil pointer dereference
  srv.branchHeadSet             engine/internal/srv/auto_delete.go:455
  srv.(*sweep).snapshots        engine/internal/srv/auto_delete.go:237
  srv.(*sweep).pool             engine/internal/srv/auto_delete.go:215
  srv.(*Server).runRetentionSweep  auto_delete.go:148
  srv.(*Server).runAutoDeletion    auto_delete.go:116

With restart: unless-stopped the engine restarts and panics again on the next tick (checkIntervalMinutes, default 5 min; every restart also re-triggers boot retrieval side effects).

Root cause

  • LVManager.GetRepo() (engine/internal/provision/thinclones/lvm/lvmanager.go:303) returns (nil, nil) — "not supported, skip" — i.e. nil repo with nil error.
  • sweep.pool (engine/internal/srv/auto_delete.go:207) checks only the error and passes the nil *models.Repo down.
  • branchHeadSet(repo) (auto_delete.go:455) reads repo.Branches → nil deref. (sweep.branches on the same nil repo is equally unprotected, and LVManager.ListProtection also returns (nil, nil).)

Who hits this

  1. Any LVM-mode instance that enables the new retention feature — supported configuration, deterministic crash loop.
  2. Any ZFS instance with a stray directory under mountDir (default /var/lib/dblab): pool discovery treats every directory there as a pool and falls back to the LVM manager for non-ZFS ones. Reproduced exactly this way: a leftover empty mountpoint dir (/var/lib/dblab/tmp-bench) was discovered as an "LVM pool" after an engine restart → first sweep tick → panic loop. (Side effect worth noting: pool rotation also selected that stray "pool" as refresh target and restored the database into it on the root filesystem.)

Retention is disabled by default, which limits the blast radius — but it's one of 4.2's headline features, so LVM users are being invited onto this path.

Suggested fix

Nil-guard the repo in sweep.pool (skip pools whose manager returns no repo), and/or make LVManager.GetRepo return an explicit "unsupported" error so the existing error branch handles it. Audit the other (nil, nil) "not supported" LVM methods (ListProtection, etc.) for the same pattern in retention/branching consumers.

Evidence

Full logs and reproduction context in the 4.2 pre-GA test ledger (confidential): #738. Sibling findings from the same test run: #740, #741 (closed).