dgtlmoon/changedetection.io · error · ModuleNotFoundError
Processor module '{processor}' not found.
Error message
Processor module '{processor}' not found. What it means
ModuleNotFoundError raised in async_update_worker when get_processor_module(processor) returns falsy — the watch's 'processor' key names a processor that isn't registered. Unlike a real import failure, this is an explicit 'module not found' guard: the requested processor name has no matching module or plugin.
Source
Thrown at changedetectionio/worker.py:177
logger.info(f"Worker {worker_id} processing watch UUID {uuid} Priority {queued_item_data.priority} URL {watch['url']}")
try:
# Retrieve signal by name to ensure thread-safe access across worker threads
watch_check_update = signal('watch_check_update')
watch_check_update.send(watch_uuid=uuid)
# Processor is what we are using for detecting the "Change"
processor = watch.get('processor', 'text_json_diff')
# Init a new 'difference_detection_processor'
# Use get_processor_module() to support both built-in and plugin processors
from changedetectionio.processors import get_processor_module
processor_module = get_processor_module(processor)
if not processor_module:
error_msg = f"Processor module '{processor}' not found."
logger.error(error_msg)
raise ModuleNotFoundError(error_msg)
update_handler = processor_module.perform_site_check(datastore=datastore,
watch_uuid=uuid)
# Allow plugins to modify/wrap the update_handler
update_handler = apply_update_handler_alter(update_handler, watch, datastore)
set_watch_minitext_status(watch, "Fetching...")
# All fetchers are now async, so call directly
await update_handler.call_browser()
# Run change detection in executor to avoid blocking event loop
# This includes CPU-intensive operations like HTML parsing (lxml/inscriptis)
# which can take 2-10ms and cause GIL contention across workers
loop = asyncio.get_event_loop()
changed_detected, update_obj, contents = await loop.run_in_executor(
executor,View on GitHub (pinned to 5d9c7c6da7)
Solutions
- Reinstall/restore the plugin or dependency that provides that processor, or upgrade back to the version that introduced it
- Edit the affected watch(es) and set processor to a core name like 'text_json_diff' (watch.json 'processor' key), or delete the orphaned watch
- Check the app startup logs for plugin load errors that silently reduced the processor registry
- Verify the exact spelling of the processor name against installed processors before re-assigning
Example fix
# before (watch.json) "processor": "my_custom_processor" # after "processor": "text_json_diff"
Defensive patterns
Strategy: validation
Validate before calling
from changedetectionio.processors import get_processor_module
for uuid, w in datastore.data['watching'].items():
if not get_processor_module(w.get('processor')):
print(f'orphaned processor on {uuid}: {w.get("processor")!r}') Type guard
def has_valid_processor(watch: dict) -> bool:
from changedetectionio.processors import get_processor_module
return get_processor_module(watch.get('processor')) is not None Try / catch
try:
await run_check(uuid)
except ModuleNotFoundError as e:
if 'Processor module' in str(e):
watch = datastore.data['watching'][uuid]
watch['processor'] = 'text_json_diff' # repair to a core processor Prevention
- Don't downgrade versions while keeping a newer datastore
- Remove or reassign watches before uninstalling a processor plugin
- Check startup logs for plugin load failures and validate watch processor keys after upgrades
When it happens
Trigger: A watch whose data['processor'] is e.g. 'text_json_diff_missing', a typo'd name, a processor provided by a plugin that was removed/disabled, or a watch created by a newer version using a processor an older instance doesn't know.
Common situations: Downgrading changedetection.io while keeping a datastore containing newer processor names; uninstalling a custom processor plugin but leaving watches assigned to it; manual datastore edits corrupting the processor key; plugin load failure at startup (bad plugin dir).
Related errors
- EmptyReply
- No screenshot available. Ensure the watch is configured to u
- UUID: {watch.get('uuid')} - Screenshot comparison failed: {e
- Cannot run, more than one price detected, this plugin is onl
- {self.filter_config.include_filters}
AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27).
Data as JSON: /api/errors/6caecd0eb032d865.
Report an issue: GitHub.