{"record":{"id":"970427d74c23cd6e","repo":"p-e-w/heretic","slug":"plugin-name-must-subclass-base-class-name","errorCode":null,"errorMessage":"Plugin '{name}' must subclass {base_class.__name__}","messagePattern":"Plugin '(.+?)' must subclass (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/heretic/plugin.py","lineNumber":145,"sourceCode":"                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:\n            raise ValueError(\n                \"Import-based plugin must use the form 'fully.qualified.module.ClassName'\"\n            )\n        module_name, class_name = name.rsplit(\".\", 1)\n        try:\n            module = importlib.import_module(module_name)\n        except ImportError as e:\n            raise ImportError(f\"Error loading plugin '{name}': {e}\") from e\n        plugin_cls = validate_class(module, class_name)\n\n    if not issubclass(plugin_cls, base_class):\n        raise TypeError(f\"Plugin '{name}' must subclass {base_class.__name__}\")\n\n    return plugin_cls\n\n\nclass Context:\n    \"\"\"\n    Runtime context passed to plugins\n\n    Provides plugin-safe access to the model.\n\n    Plugins must use `get_responses(...)`, `get_logits(...)`, etc.\n    Direct access to the underlying Model is intentionally not exposed.\n    \"\"\"\n\n    def __init__(self, settings: HereticSettings, model: Model) -> None:\n        self._model = model\n        self._settings = settings\n        self._responses_cache: dict[tuple[tuple[str, str], ...], list[str]] = {}","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/p-e-w/heretic/blob/bedb94ef117a271532ac2058447fbc165d5051bd/src/heretic/plugin.py#L127-L163","documentation":"The plugin class was found and loaded, but issubclass(plugin_cls, base_class) failed: the class does not inherit from the required plugin base (e.g. Scorer). load_plugin() raises TypeError because loading a non-plugin class would break the evaluator contract downstream.","triggerScenarios":"Passing 'mypkg.models.Thing' where Thing subclasses nothing relevant; a file plugin exporting a helper class instead of the plugin class; the base class changed between heretic versions so an old plugin no longer subclasses the new base (e.g. Plugin API refactor).","commonSituations":"Pointing the config at the wrong class in a module that defines several; plugin written for an older heretic release with a different base class; copy-paste left a plain class unmodified.","solutions":["Make the class inherit from the expected base (e.g. class MyScorer(Scorer)).","Point the plugin name at the class that actually subclasses the base.","After a heretic upgrade, update the plugin to the new base-class API and re-export it."],"exampleFix":"# before\nclass MyScorer:\n    ...\n# after\nfrom heretic.scorers import Scorer\nclass MyScorer(Scorer):\n    ...","handlingStrategy":"type-guard","validationCode":"from heretic.plugin import load_plugin\ndef check_plugin(name: str, base: type) -> bool:\n    try:\n        return issubclass(load_plugin(name, base), base)\n    except (ValueError, ImportError, TypeError):\n        return False","typeGuard":"def is_scorer(obj: object) -> bool:\n    return inspect.isclass(obj) and issubclass(obj, Scorer)","tryCatchPattern":"try:\n    cls = load_plugin(name, Scorer)\nexcept TypeError as e:\n    sys.exit(f\"{e} — make sure your class subclasses Scorer\")","preventionTips":["Always inherit from the required base class (Scorer, etc.).","After upgrading heretic, re-check your plugin against the current base-class API.","Export only the intended plugin class under the name you reference."],"tags":["plugin","python","type-error"],"backgroundTag":"plugin-base-class-mismatch","analyzedSha":"bedb94ef117a271532ac2058447fbc165d5051bd","analyzedAt":"2026-08-29T08:38:06.692Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}