{"record":{"id":"205538d2bc1c5e76","repo":"p-e-w/heretic","slug":"could-not-load-plugin-name-invalid-module-spe","errorCode":null,"errorMessage":"Could not load plugin '{name}' (invalid module spec)","messagePattern":"Could not load plugin '(.+?)' \\(invalid module spec\\)","errorType":"exception","errorClass":"ImportError","httpStatus":null,"severity":"error","filePath":"src/heretic/plugin.py","lineNumber":115,"sourceCode":"        plugin_path = Path(file_path)\n        if not plugin_path.is_absolute():\n            plugin_path = Path.cwd() / plugin_path\n        plugin_path = plugin_path.resolve()\n\n        if not plugin_path.is_file():\n            raise ImportError(f\"Plugin file '{plugin_path}' does not exist\")\n\n        # We're writing directly to the sys.modules dict,\n        # so the typical restrictions on module names\n        # (no dots, slashes, etc.) don't apply.\n        module_name = f\"heretic_plugin_{plugin_path}\"\n\n        # Reuse already-loaded modules to avoid re-executing the plugin on repeated loads.\n        module = sys.modules.get(module_name)\n        if module is None:\n            spec = importlib.util.spec_from_file_location(module_name, plugin_path)\n            if spec is None or spec.loader is None:\n                raise ImportError(\n                    f\"Could not load plugin '{name}' (invalid module spec)\"\n                )\n\n            module = importlib.util.module_from_spec(spec)\n\n            # Cache before executing to match normal import semantics and allow\n            # circular imports. If execution fails, remove the entry.\n            sys.modules[module_name] = module\n            try:\n                spec.loader.exec_module(module)\n            except Exception:\n                sys.modules.pop(module_name, None)\n                raise\n\n        plugin_cls = validate_class(module, class_name)\n    # Fully-qualified import path, e.g \"heretic.scorers.keyword_rate.KeywordRate\".\n    else:\n        if \".\" not in name:","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/p-e-w/heretic/blob/bedb94ef117a271532ac2058447fbc165d5051bd/src/heretic/plugin.py#L97-L133","documentation":"importlib.util.spec_from_file_location() returned no spec or no loader, so the plugin file cannot be turned into a module. load_plugin() raises this ImportError early rather than crashing with an opaque AttributeError later.","triggerScenarios":"spec_from_file_location returning None for the resolved plugin_path — typically a nonexistent file slipping through, an unsupported extension (non-.py already filtered, but e.g. broken symlinks or special files), or a loader mismatch on unusual filesystem entries.","commonSituations":"Files added to a cache/overlay that disappeared before import; permission or filesystem oddities in containers; Python builds lacking the source-file loader for the given path type.","solutions":["Confirm plugin_path.is_file() is True right before loading (race conditions are the usual cause).","Verify the Python runtime supports SourceFileLoader for this path (standard CPython does for .py).","Re-check that the path contains no invalid characters for the loader and retry with a plain, local .py file."],"exampleFix":"# before (blind call)\nplugin_cls = load_plugin(args.scorer, Scorer)\n# after\ntarget = Path(args.scorer.split(':',1)[0]).resolve()\nif not target.is_file():\n    raise FileNotFoundError(f\"missing plugin file: {target}\")\nplugin_cls = load_plugin(args.scorer, Scorer)","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef assert_loadable(name: str) -> None:\n    target = Path(name.rsplit(\":\", 1)[0]).resolve()\n    if not target.is_file():\n        raise FileNotFoundError(target)","typeGuard":null,"tryCatchPattern":"try:\n    cls = load_plugin(name, Scorer)\nexcept ImportError as e:\n    logging.error(\"plugin load failed: %s\", e)\n    raise","preventionTips":["Re-stat the file immediately before loading to avoid races.","Use plain local .py files on standard CPython."],"tags":["plugin","python","import"],"backgroundTag":"invalid-module-spec","analyzedSha":"bedb94ef117a271532ac2058447fbc165d5051bd","analyzedAt":"2026-08-29T08:38:06.692Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}