Skip to content

v1.6.0 regression: _cleanup() skipped during events.json→events.db migration, orphaning all pre-migration snapshots #640

@cshuttle

Description

@cshuttle

Checklist

  • I'm running the newest version of LLM Vision
  • I have enabled debug logging for the integration.
  • I have filled out the issue template to the best of my ability.
  • This issue only contains 1 issue (if you have multiple issues, open one issue for each issue).
  • This is a bug and not a feature request.
  • I have searched open issues for my problem.

Describe the issue

After upgrading to v1.6.0 (which migrates storage from events.json to events.db), all snapshot files accumulated before the upgrade become permanent orphans that are never deleted — even with retention_time correctly configured.

Observed: 7,136 snapshot files dating back months, totalling 2.5 GB, with only 44 recent files actually referenced in the database. No cleanup had occurred despite retention_time: 7 being set.

Root cause

The bug is a race condition introduced in Timeline.__init__ in v1.6.0. All four async tasks are fired concurrently, but _migrating is set to True before any of them run:

# timeline.py — Timeline.__init__
self._migrating = True                               # set immediately
self.hass.async_create_task(self._initialize_db())
self.hass.async_create_task(self._migrate())         # sets _migrating=False when done
self.hass.async_create_task(self.load_events())
self.hass.async_create_task(self._cleanup())         # ← sees _migrating=True → returns immediately

_cleanup() has this guard (line 938):

if getattr(self, "_migrating", False):
    return

Because all tasks run concurrently, _cleanup() always sees _migrating = True when a migration is in progress. Once _migrate() finishes and sets _migrating = False, there is no subsequent cleanup scheduled — so every snapshot file that pre-dates the migration becomes a permanent orphan.

The same guard also blocks the await timeline._cleanup() call in async_setup_entry (__init__.py line 137) if the background migration task hasn't completed yet.

Impact

Any user who upgraded from a version using events.json to v1.6.0+ will have unbounded snapshot accumulation. The retention system appears to be working (DB records are purged via _purge_expired_events) but the physical files are never swept because _cleanup() never runs successfully post-migration.

Note: this is a regression from the fix in v1.5.2 for issue #445 — the general retention case works correctly on a fresh install, but the one-time migration path bypasses cleanup entirely.

Suggested fix

Call _cleanup() at the end of _migrate() after setting _migrating = False, rather than racing it as a concurrent task:

async def _migrate(self):
    try:
        # ... migration logic ...
    finally:
        self._migrating = False
        await self._cleanup()  # run cleanup now that migration is complete

This ensures that any files orphaned by the migration (i.e., files that existed before events.db was created and are not referenced by any DB record) are swept immediately.

Workaround

Until a fix is released, users affected by this can run a one-time cleanup manually:

import sqlite3, os

DB_PATH = "/homeassistant/llmvision/events.db"
SNAPSHOTS_DIR = "/media/llmvision/snapshots"

con = sqlite3.connect(DB_PATH)
cur = con.cursor()
cur.execute("SELECT key_frame FROM events WHERE key_frame IS NOT NULL")
protected = {os.path.basename(row[0]).lower() for row in cur.fetchall()}
con.close()

removed = 0
for f in os.listdir(SNAPSHOTS_DIR):
    if f.lower() not in protected:
        os.remove(os.path.join(SNAPSHOTS_DIR, f))
        removed += 1
print(f"Removed {removed} orphaned snapshots")

Version

  • LLM Vision: 1.6.0
  • Home Assistant: 2025.10.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions