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
- Read the appended original error '{e}' — it names the real cause (e.g. 'Image cannot be cropped', 'cannot identify image file')
- Verify the element selector / drawn region still resolves to a non-empty area on the page
- Test with the region cleared (whole-page compare) to isolate crop-related failures
- Pin/align Pillow and numpy versions known to work with the SSIM handler
- 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
- Validate element selectors regularly (the built-in preview) so crop regions stay non-empty
- Keep Pillow/numpy versions aligned with the project's requirements
- Treat the appended inner message as the real diagnostic, not the wrapper text
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
- No screenshot available. Ensure the watch is configured to u
- Cannot run, more than one price detected, this plugin is onl
- {self.filter_config.include_filters}
- Processor module '{processor}' not found.
- abort(403)
AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27).
Data as JSON: /api/errors/45d95589dfc68f34.
Report an issue: GitHub.