dgtlmoon/changedetection.io · critical · Exception

Migration failed: Could not save watch {uuid}. url-watches.j

Error message

Migration failed: Could not save watch {uuid}. url-watches.json remains intact, safe to retry. Error: {e}

What it means

A generic Exception raised inside update_26's migrate_legacy_db_format (Phase 1) when saving a single migrated watch to <uuid>/watch.json raises. The migration wraps each watch save so one bad record aborts with context; url-watches.json is deliberately left untouched so the migration can be retried safely.

Source

Thrown at changedetectionio/store/updates.py:600

        logger.info("Populating settings from legacy data...")
        watch_count = len(self.data['watching'])
        logger.success(f"Loaded {watch_count} watches from legacy format")

        # Phase 1: Save all watches to individual files
        logger.critical(f"Phase 1/4: Saving {watch_count} watches to individual watch.json files...")

        saved_count = 0
        for uuid, watch in self.data['watching'].items():
            try:
                watch.commit()
                saved_count += 1

                if saved_count % 100 == 0:
                    logger.info(f"  Progress: {saved_count}/{watch_count} watches migrated...")

            except Exception as e:
                logger.error(f"Failed to save watch {uuid}: {e}")
                raise Exception(
                    f"Migration failed: Could not save watch {uuid}. "
                    f"url-watches.json remains intact, safe to retry. Error: {e}"
                )

        logger.critical(f"Phase 1 complete: Saved {saved_count} watches")

        # Phase 2: Verify all files exist
        logger.critical("Phase 2/4: Verifying all watch.json files were created...")

        missing = []
        for uuid in self.data['watching'].keys():
            watch_json = os.path.join(self.datastore_path, uuid, "watch.json")
            if not os.path.isfile(watch_json):
                missing.append(uuid)

        if missing:
            raise Exception(
                f"Migration failed: {len(missing)} watch files missing: {missing[:5]}... "

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Read the appended inner error '{e}' — it identifies the actual save failure for that uuid
  2. Fix the specific watch entry in url-watches.json (remove/repair the offending uuid) and restart to retry
  3. Check disk space and directory permissions on the datastore path
  4. Back up url-watches.json before retrying — it is your recovery point, keep it intact
Defensive patterns

Strategy: try-catch

Validate before calling

# before upgrading: back up and sanity-parse the legacy file
import json
data = json.load(open('url-watches.json'))
assert all(isinstance(v, dict) for v in data.get('watching', {}).values())

Try / catch

try:
    update_26(datastore)
except Exception as e:
    if 'Migration failed' in str(e):
        # url-watches.json is intact; fix cause (disk/perms/bad entry) and restart to retry
        backup_datastore(); investigate(str(e))

Prevention

When it happens

Trigger: Running the v26 datastore migration on a legacy url-watches.json where one watch entry contains data that cannot be serialized/saved (corrupt entry, unserializable value, permission/disk error on that subdirectory). The inner save's exception text is appended after 'Error: '.

Common situations: Upgrading an old changedetection.io instance with hand-edited or partially corrupt url-watches.json; datastore dir with mixed ownership; disk filling mid-migration.

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/60ddae6d25e00e17. Report an issue: GitHub.