{"record":{"id":"bc5f31be06db0e1b","repo":"HKUDS/nanobot","slug":"channel-plugin-label-module-must-be-an-absolute","errorCode":null,"errorMessage":"channel plugin {label} module must be an absolute import path","messagePattern":"channel plugin (.+?) module must be an absolute import path","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"nanobot/channels/plugin.py","lineNumber":121,"sourceCode":"        if not callable(factory):\n            raise ImportError(\n                f\"Channel plugin '{self.name}' connector '{self.connector}' is not callable\"\n            )\n        connector = factory()\n        if not callable(getattr(connector, \"handle\", None)):\n            raise ImportError(\n                f\"Channel plugin '{self.name}' connector '{self.connector}' \"\n                \"does not provide handle()\"\n            )\n        return connector\n\n\ndef _target_parts(target: str, *, label: str) -> tuple[str, str]:\n    module_name, separator, attr_name = target.partition(\":\")\n    if not separator or not module_name or not attr_name:\n        raise ValueError(f\"channel plugin {label} must use 'module:attribute' syntax\")\n    if not all(part.isidentifier() for part in module_name.split(\".\")):\n        raise ValueError(f\"channel plugin {label} module must be an absolute import path\")\n    if not attr_name.isidentifier():\n        raise ValueError(f\"channel plugin {label} attribute must be a Python identifier\")\n    return module_name, attr_name\n\n\ndef has_channel_package(name: str) -> bool:\n    \"\"\"Return whether *name* owns a dependency-free package manifest.\"\"\"\n    if _CHANNEL_PACKAGE_NAME.fullmatch(name) is None:\n        return False\n    return files(\"nanobot.channels\").joinpath(name, \"manifest.py\").is_file()\n\n\n@lru_cache(maxsize=None)\ndef load_channel_package(name: str) -> ChannelPlugin | None:\n    \"\"\"Load one package manifest without importing its runtime.\"\"\"\n    if not has_channel_package(name):\n        return None\n","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/HKUDS/nanobot/blob/42f37dc4c0e409eac7818a82d165c5f2757bc075/nanobot/channels/plugin.py#L103-L139","documentation":"Validation in _target_parts: the module portion of a 'module:attribute' target is relative (e.g. starts with a dot or is otherwise not absolute), which importlib.import_module would resolve against an unpredictable package context. The guard requires fully-qualified absolute module paths.","triggerScenarios":"Thrown at nanobot/channels/plugin.py:121 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use the fully qualified module path, e.g. 'nanobot.channels.foo.runtime:FooChannel'","Remove leading dots or relative prefixes from the module part"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"42f37dc4c0e409eac7818a82d165c5f2757bc075","analyzedAt":"2026-08-26T00:18:52.276Z","contentChangedAt":"2026-08-26T00:18:52.276Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}