dgtlmoon/changedetection.io · critical · Exception

Migration failed: Could not create changedetection.json. url

Error message

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

What it means

A generic Exception raised in Phase 3 of migrate_legacy_db_format when self._save_settings() fails while writing the new changedetection.json. This is the final artifact of the legacy migration; on failure the old url-watches.json is preserved so nothing is lost and the migration can be retried.

Source

Thrown at changedetectionio/store/updates.py:631

            if not os.path.isfile(watch_json):
                missing.append(uuid)

        if missing:
            raise Exception(
                f"Migration failed: {len(missing)} watch files missing: {missing[:5]}... "
                f"url-watches.json remains intact, safe to retry."
            )

        logger.critical(f"Phase 2 complete: Verified {watch_count} watch files")

        # Phase 3: Create new settings file
        logger.critical("Phase 3/4: Creating changedetection.json...")

        try:
            self._save_settings()
        except Exception as e:
            logger.error(f"Failed to create changedetection.json: {e}")
            raise Exception(
                f"Migration failed: Could not create changedetection.json. "
                f"url-watches.json remains intact, safe to retry. Error: {e}"
            )

        # Phase 4: Verify settings file exists
        logger.critical("Phase 4/4: Verifying changedetection.json exists...")
        changedetection_json_new_schema=os.path.join(self.datastore_path, "changedetection.json")
        if not os.path.isfile(changedetection_json_new_schema):
            import sys
            logger.critical("Migration failed, changedetection.json not found after update ran!")
            sys.exit(1)


        logger.critical("Phase 4 complete: Verified changedetection.json exists")

        # Success! Now reload from new format
        logger.critical("Reloading datastore from new format...")
        # write it to disk, it will be saved without ['watching'] in the JSON db because we find it from disk glob

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Check free space on the datastore volume and free some if full (Phase 1 writes can consume a lot)
  2. Verify the datastore dir is writable by the app user and not mounted read-only
  3. Fix the underlying save error shown after 'Error: ', then restart the app to retry the migration
  4. Keep the url-watches.json backup until the app boots cleanly with changedetection.json present
Defensive patterns

Strategy: retry

Validate before calling

import shutil
# Phase 1 writes one dir per watch; ensure room before starting
free = shutil.disk_usage(datastore_path).free
assert free > 50 * 1024 * 1024, 'insufficient space for migration artifacts'

Try / catch

try:
    update_26(datastore)
except Exception as e:
    if 'Could not create changedetection.json' in str(e):
        free_space(); restart_app()  # retried safely, url-watches.json preserved

Prevention

When it happens

Trigger: The settings save hitting an I/O problem — disk full (ENOSPC surfaces as OSError from save_json_atomic), read-only filesystem, or permission loss on the datastore root — exactly when the migration tries to materialize changedetection.json.

Common situations: Disk exhausted by the thousands of watch.json files written in Phase 1 (the most common cause); datastore mounted read-only; ownership mismatch after container user change.

Related errors


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