dgtlmoon/changedetection.io · error · ProcessorException

UUID: {watch.get('uuid')} - Screenshot comparison failed: {e

Error message

UUID: {watch.get('uuid')} - Screenshot comparison failed: {e}

What it means

A catch-all ProcessorException raised in run_changedetection of the image_ssim_diff processor. Any unexpected exception thrown by the screenshot comparison handler (decode, crop, SSIM computation, Pillow errors, etc.) is logged and re-raised as a ProcessorException with the watch UUID and the original error text appended.

Source

Thrown at changedetectionio/processors/image_ssim_diff/processor.py:226

            thread.start()
            thread.join(timeout=60)

            if exception_container[0]:
                raise exception_container[0]

            # Subprocess returns only the change score - we decide if it's a "change"
            change_score = result_container[0]
            if change_score is None:
                raise RuntimeError("Image comparison subprocess returned no result")

            changed_detected = change_score > min_change_percentage
            logger.info(f"UUID: {watch.get('uuid')} -  {process_screenshot_handler.IMPLEMENTATION_NAME}: {change_score:.2f}% pixels changed, pixel_diff_threshold_sensitivity: {pixel_difference_threshold_sensitivity:.0f} score={change_score:.2f}%, min_change_threshold={min_change_percentage}%")

        except Exception as e:
            logger.error(f"UUID: {watch.get('uuid')} - Failed to compare screenshots: {e}")
            logger.trace(f"UUID: {watch.get('uuid')} - Processed in {time.time() - now:.3f}s")

            raise ProcessorException(
                message=f"UUID: {watch.get('uuid')} - Screenshot comparison failed: {e}",
                url=watch.get('url')
            )

        # Return results
        update_obj = {
            'previous_md5': hashlib.md5(self.screenshot).hexdigest(),
            'last_error': False
        }

        if changed_detected:
            logger.info(f"UUID: {watch.get('uuid')} - Change detected using OpenCV! Score: {change_score:.2f}")
        else:
            logger.debug(f"UUID: {watch.get('uuid')} - No significant change using OpenCV. Score: {change_score:.2f}")
        logger.trace(f"UUID: {watch.get('uuid')} - Processed in {time.time() - now:.3f}s")

        return changed_detected, update_obj, self.screenshot

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Read the appended original error '{e}' — it names the real cause (e.g. 'Image cannot be cropped', 'cannot identify image file')
  2. Verify the element selector / drawn region still resolves to a non-empty area on the page
  3. Test with the region cleared (whole-page compare) to isolate crop-related failures
  4. Pin/align Pillow and numpy versions known to work with the SSIM handler
  5. Re-capture the snapshot: delete the watch's snapshot dir so a fresh screenshot is stored
Defensive patterns

Strategy: try-catch

Try / catch

try:
    changed, update_obj, shot = handler.run_changedetection(watch, ...)
except ProcessorException as e:
    logger.error('SSIM check failed for %s: %s', watch['uuid'], e)
    # mark watch errored, keep previous snapshot, do not retry blindly
    watch['last_error'] = str(e)

Prevention

When it happens

Trigger: The screenshot-comparison code path raising: corrupt/truncated screenshot bytes fed to Pillow, an invalid crop region/element selection (e.g. element selector matched nothing so crop box is empty), incompatible Pillow/numpy versions, or zero-dimension images after cropping.

Common situations: The watched page's element selector no longer matches (crop region empty); a site returning an HTML error page saved as screenshot; upgrading Pillow/numpy and hitting API breakage; extremely large screenshots causing memory errors.

Related errors


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