{"record":{"id":"6caecd0eb032d865","repo":"dgtlmoon/changedetection.io","slug":"processor-module-processor-not-found","errorCode":null,"errorMessage":"Processor module '{processor}' not found.","messagePattern":"Processor module '(.+?)' not found\\.","errorType":"exception","errorClass":"ModuleNotFoundError","httpStatus":null,"severity":"error","filePath":"changedetectionio/worker.py","lineNumber":177,"sourceCode":"                logger.info(f\"Worker {worker_id} processing watch UUID {uuid} Priority {queued_item_data.priority} URL {watch['url']}\")\n\n                try:\n                    # Retrieve signal by name to ensure thread-safe access across worker threads\n                    watch_check_update = signal('watch_check_update')\n                    watch_check_update.send(watch_uuid=uuid)\n\n                    # Processor is what we are using for detecting the \"Change\"\n                    processor = watch.get('processor', 'text_json_diff')\n\n                    # Init a new 'difference_detection_processor'\n                    # Use get_processor_module() to support both built-in and plugin processors\n                    from changedetectionio.processors import get_processor_module\n                    processor_module = get_processor_module(processor)\n\n                    if not processor_module:\n                        error_msg = f\"Processor module '{processor}' not found.\"\n                        logger.error(error_msg)\n                        raise ModuleNotFoundError(error_msg)\n\n                    update_handler = processor_module.perform_site_check(datastore=datastore,\n                                                                         watch_uuid=uuid)\n\n                    # Allow plugins to modify/wrap the update_handler\n                    update_handler = apply_update_handler_alter(update_handler, watch, datastore)\n\n                    set_watch_minitext_status(watch, \"Fetching...\")\n\n                    # All fetchers are now async, so call directly\n                    await update_handler.call_browser()\n\n                    # Run change detection in executor to avoid blocking event loop\n                    # This includes CPU-intensive operations like HTML parsing (lxml/inscriptis)\n                    # which can take 2-10ms and cause GIL contention across workers\n                    loop = asyncio.get_event_loop()\n                    changed_detected, update_obj, contents = await loop.run_in_executor(\n                        executor,","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/worker.py#L159-L195","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"# before (watch.json)\n\"processor\": \"my_custom_processor\"\n# after\n\"processor\": \"text_json_diff\"","handlingStrategy":"validation","validationCode":"from changedetectionio.processors import get_processor_module\nfor uuid, w in datastore.data['watching'].items():\n    if not get_processor_module(w.get('processor')):\n        print(f'orphaned processor on {uuid}: {w.get(\"processor\")!r}')","typeGuard":"def has_valid_processor(watch: dict) -> bool:\n    from changedetectionio.processors import get_processor_module\n    return get_processor_module(watch.get('processor')) is not None","tryCatchPattern":"try:\n    await run_check(uuid)\nexcept ModuleNotFoundError as e:\n    if 'Processor module' in str(e):\n        watch = datastore.data['watching'][uuid]\n        watch['processor'] = 'text_json_diff'  # repair to a core processor","preventionTips":["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"],"tags":["processor","plugin","module-not-found","datastore"],"backgroundTag":"module-not-found","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}