{"record":{"id":"8c89a1f99e106f04","repo":"p-e-w/heretic","slug":"plugin-namespace-namespace-must-be-a-table-obj","errorCode":null,"errorMessage":"Plugin namespace [{namespace}] must be a table/object, got {type(cur).__name__}","messagePattern":"Plugin namespace \\[(.+?)\\] must be a table/object, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/heretic/plugin.py","lineNumber":40,"sourceCode":"T = TypeVar(\"T\")\n\n\ndef get_plugin_namespace(\n    model_extra: dict[str, Any] | None, namespace: str\n) -> dict[str, Any]:\n    \"\"\"\n    Returns the config dict from the `[<namespace>]` TOML table.\n    \"\"\"\n    cur: Any = model_extra\n    for part in namespace.split(\".\"):\n        if not isinstance(cur, dict):\n            return {}\n        cur = cur.get(part)\n\n    if cur is None:\n        return {}\n    if not isinstance(cur, dict):\n        raise TypeError(\n            f\"Plugin namespace [{namespace}] must be a table/object, got {type(cur).__name__}\"\n        )\n    return cur\n\n\ndef is_builtin_plugin(name: str) -> bool:\n    \"\"\"\n    Whether the plugin name refers to a plugin that ships with Heretic.\n\n    Only built-in plugins can be resolved when reproducing a model, so external\n    plugins (file paths or third-party import paths) disable the reproducibility\n    offer during upload.\n    \"\"\"\n    return name.startswith(\"heretic.scorers.\")\n\n\ndef load_plugin(\n    name: str,","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/p-e-w/heretic/blob/bedb94ef117a271532ac2058447fbc165d5051bd/src/heretic/plugin.py#L22-L58","documentation":"get_plugin_namespace walks the config table path segment by segment for a plugin's namespace. If the value found at that path exists but is not a table/dict (e.g. a string or number), the structure is invalid, so it raises TypeError naming the namespace path and the offending type.","triggerScenarios":"A config.toml section expected to be a table is instead a scalar — e.g. writing `refusal = \"on\"` instead of `[plugins.refusal]`, or a dotted key collision where an intermediate path resolves to a non-table value.","commonSituations":"Typos in config.toml turning a section header into a key, TOML dotted-key conflicts (defining both a.b = 1 and [a.b.c]), or JSON configs where an object was replaced by a string.","solutions":["Change the config value at that namespace path to a table/object ([plugin.<namespace>])","Check for TOML dotted-key conflicts that redefine a parent key as a scalar","Print/inspect the loaded config to confirm the type at the reported path"],"exampleFix":"// before (config.toml)\nmyplugin = \"enabled\"\n// after (config.toml)\n[myplugin]\nenabled = true\n","handlingStrategy":"type-guard","validationCode":"import tomllib\nwith open(\"config.toml\", \"rb\") as f:\n    cfg = tomllib.load(f)\nns = cfg.get(\"plugins\", {}).get(\"myplugin\")\nif ns is not None and not isinstance(ns, dict):\n    raise TypeError(\"plugins.myplugin must be a table\")","typeGuard":"def is_table(value) -> bool:\n    return value is None or isinstance(value, dict)","tryCatchPattern":"try:\n    settings = get_plugin_namespace(config, \"myplugin\")\nexcept TypeError as e:\n    logger.error(\"Bad config structure: %s\", e)\n    raise SystemExit(1)","preventionTips":["Use explicit [section] headers instead of assigning scalars for plugin namespaces","Avoid TOML dotted-key patterns that can redefine parents as scalars","Validate config structure with a schema (pydantic/jsonschema) before use"],"tags":["config","type-error","toml"],"backgroundTag":"invalid-config-type","analyzedSha":"bedb94ef117a271532ac2058447fbc165d5051bd","analyzedAt":"2026-08-29T08:38:06.692Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}