Root cause of the flaky pytest segfault (exit 139): the background scan and enrichment daemon threads (_scan_runner/_enrich_runner) use the shared MetadataDB connection, but test fixtures closed that connection in teardown without stopping them. A daemon thread mid-query on a freed SQLite conn is a native use-after-free → SIGSEGV. The app's startup kicks a scan, so almost any app-booting fixture was vulnerable. It only surfaced now because got-feedback/feedBack#728 added a push trigger, so ci/test runs on every push to main.
Fix: server.py retains the scan/enrich thread handles and adds _join_background_db_threads(); every test fixture now joins the workers before conn.close(). Verified: the full suite runs to completion (no segfault) where it previously crashed at ~25%.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the dev-ops lens's #1 finding from the library charrette
(got-feedback/feedBack#636 item 1): scores, favorites, playlists, and play
history — the only library state a rescan can't rebuild — were absent from
the settings backup. Now GET /api/settings/export carries an additive
`core_server_files` section:
- a CONSISTENT snapshot of web_library.db via the SQLite online-backup API
(a complete single file even while the server runs; taken under the
MetadataDB write lock), base64-encoded;
- custom playlist covers + avatar (CONFIG_DIR/playlist_covers, /avatars),
walked with the existing _walk_export_paths machinery.
Restore is DB-safe:
- POST /api/settings/import STAGES the DB to web_library.db.restore (never
over the live, open file); _apply_pending_db_restore swaps it in at the
next startup BEFORE the connection opens, clearing stale -wal/-shm so a
stale WAL can't be replayed onto the restored file. Response sets
`restart_required` + a warning; custom art applies immediately.
- The staged DB is integrity-checked (open + PRAGMA quick_check) at import
AND again at startup before the live DB is touched — a corrupt/truncated
restore is refused/discarded and the live DB is left intact, so a bad
bundle can never brick startup or lose data.
- Export hard-fails (500) if the snapshot can't be produced (no silent
DB-less backup); a partial import disarms its own staged restore.
Backward-compatible: older servers ignore the new section; a bundle without
it imports as before. Known gap: custom uploaded *song* art is still
commingled with the rebuildable thumbnail cache in art_cache/, so it isn't
bundled yet (tracked follow-up on #636).
Tests: tests/test_settings_export_library_db.py (snapshot consistency,
staged-not-live restore, sidecar clearing, corrupt-DB refusal at import +
startard, traversal rejection, export hard-fail, disarm-on-failure, full
round-trip).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>