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
- Read the appended inner error '{e}' — it identifies the actual save failure for that uuid
- Fix the specific watch entry in url-watches.json (remove/repair the offending uuid) and restart to retry
- Check disk space and directory permissions on the datastore path
- 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
- Always back up url-watches.json and the datastore before version upgrades
- Ensure ample disk space before migrating large watch collections
- Never run two versions against one datastore during upgrade windows
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
- Migration failed: {len(missing)} watch files missing: {missi
- Migration failed: Could not create changedetection.json. url
- Disk full: Cannot save {label}
- Disk quota exceeded: Cannot save {label}
- I/O error saving {label}: {e}
AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27).
Data as JSON: /api/errors/60ddae6d25e00e17.
Report an issue: GitHub.