{"record":{"id":"d03f17c535f88764","repo":"influxdata/influxdb","slug":"plugin-root-has-no-parent-directory","errorCode":null,"errorMessage":"Plugin root has no parent directory","messagePattern":"Plugin root has no parent directory","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"influxdb3_py_api/src/system_py.rs","lineNumber":329,"sourceCode":"            .getattr(\"name\")\n            .ok()\n            .and_then(|name| name.extract::<String>().ok())\n            .is_some_and(|name| name == module_name)\n}\n\n/// Loads a Python function from a multi-file plugin module.\n///\n/// This function temporarily adds the plugin's parent directory to Python's sys.path,\n/// imports the module, retrieves the function, and then cleans up sys.path.\nfn load_function_from_module<'py>(\n    py: Python<'py>,\n    plugin_root: &Path,\n    function_name: &str,\n    missing_fn_error: ExecutePluginError,\n) -> Result<Bound<'py, PyAny>, ExecutePluginError> {\n    let parent_dir = plugin_root\n        .parent()\n        .ok_or_else(|| anyhow!(\"Plugin root has no parent directory\"))?;\n    let module_name = plugin_root\n        .file_name()\n        .and_then(|n| n.to_str())\n        .ok_or_else(|| anyhow!(\"Invalid plugin directory name\"))?;\n\n    let sys = py.import(\"sys\").map_err(anyhow::Error::from)?;\n    let sys_path = sys.getattr(\"path\").map_err(anyhow::Error::from)?;\n\n    let parent_dir_str = parent_dir\n        .to_str()\n        .ok_or_else(|| anyhow!(\"Invalid UTF-8 in parent directory path\"))?;\n\n    // Acquire while attached to the Python runtime; `lock_py_attached` detaches\n    // internally if the lock is contended, avoiding a GIL/lock deadlock.\n    let _sys_path_guard = SYS_PATH_LOCK.lock_py_attached(py);\n\n    // Add parent directory to sys.path\n    sys_path","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/influxdata/influxdb/blob/06200ef96ba82c5f6727e5038a83af8e722c6875/influxdb3_py_api/src/system_py.rs#L311-L347","documentation":"load_function_from_module treats plugin_root as a package directory whose parent is placed on sys.path; if the path has no parent (e.g. a bare relative component like Path('plugins') at filesystem root semantics, or an empty/root path), .parent() returns None and this anyhow error is raised. It means the plugin root path is malformed for module-style loading.","triggerScenarios":"load_plugin_function given a plugin_root Path with no parent directory — e.g. a bare name like Path(\"my_plugin\"), a root path like Path(\"/\"), or an empty path — instead of a full directory path like /path/to/plugins/my_plugin.","commonSituations":"Passing just the plugin directory name read from config while the base directory wasn't joined; resolving plugin roots from env vars that are empty or '/'; incorrect working-directory assumptions making the path resolve to a bare component.","solutions":["Pass an absolute path to the plugin directory, e.g. Path('/opt/influxdb3/plugins/my_plugin'), not just the directory name.","Join the configured plugin base directory with the plugin name before calling: base_dir / plugin_name.","Guard the path before loading: if not plugin_root.is_absolute(): plugin_root = base_dir / plugin_root."],"exampleFix":"# before\nroot = Path(os.environ.get('PLUGIN_DIR', ''))  # empty -> no parent\n# after\nroot = (Path('/opt/influxdb3/plugins') / plugin_name).resolve()","handlingStrategy":"validation","validationCode":"from pathlib import Path\nroot = Path(plugin_name)\nif not root.is_absolute():\n    root = (Path('/opt/influxdb3/plugins') / plugin_name).resolve()","typeGuard":null,"tryCatchPattern":"if not plugin_root.parent:\n    plugin_root = base_dir / plugin_root  # normalize before calling the loader","preventionTips":["Always pass absolute, resolved paths for plugin directories.","Join a configured base plugin directory with the plugin name instead of passing bare names.","Validate plugin path config values at startup (non-empty, has parent, exists)."],"tags":["rust","python","plugin","path"],"backgroundTag":"invalid-config-value","analyzedSha":"06200ef96ba82c5f6727e5038a83af8e722c6875","analyzedAt":"2026-09-19T12:55:30.003Z","contentChangedAt":"2026-09-19T12:55:30.003Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}