{"record":{"id":"720a7de8bad760f1","repo":"influxdata/influxdb","slug":"invalid-plugin-directory-name","errorCode":null,"errorMessage":"Invalid plugin directory name","messagePattern":"Invalid plugin directory name","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"influxdb3_py_api/src/system_py.rs","lineNumber":333,"sourceCode":"}\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\n        .call_method1(\"insert\", (0, parent_dir_str))\n        .map_err(anyhow::Error::from)?;\n\n    // A plugin dir added after FileFinder cached the parent is invisible until","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/influxdata/influxdb/blob/06200ef96ba82c5f6727e5038a83af8e722c6875/influxdb3_py_api/src/system_py.rs#L315-L351","documentation":"Thrown by load_function_from_module in influxdb3_py_api when loading a Python plugin from a directory. The plugin root path's final component (the directory name, used as the Python module name) cannot be converted to a UTF-8 string via Path::file_name().to_str(), typically because the path is malformed (e.g. ends in '..' or is a bare root) or contains non-UTF-8 bytes.","triggerScenarios":"Calling load_plugin_function with a plugin_directory whose final component is not valid UTF-8 (non-UTF-8 bytes from filesystem encoding), or a path where file_name() resolves oddly (trailing path separators, root paths like '/', or '..' components).","commonSituations":"Plugin directories created with shell-generated names containing raw bytes; paths passed with trailing slashes ('/plugins/my_plugin/') or dot components; plugin dir supplied from an env var or config built with non-UTF-8 locale filesystem encoding (rare on Linux, possible on some systems).","solutions":["Rename the plugin directory so its name is plain ASCII/UTF-8 and contains no '..' or trailing separators.","Normalize the plugin_directory value before passing it (strip trailing '/', resolve '.'/'..' with std::path::Path::normalize/canonicalize).","Verify where the path comes from (config file, env var) and ensure it is valid UTF-8: `echo \"$PLUGIN_DIR\" | iconv -f utf-8 -t utf-8` or `PathBuf::into_os_string().into_string()` in Rust to validate early.","If loading fails at startup, list the plugins directory (`ls`) to spot oddly named directories."],"exampleFix":"// before\nlet dir = std::env::var(\"PLUGIN_DIR\").unwrap(); // may contain trailing slash or non-UTF-8\nload_plugin_function(&dir, ...);\n// after\nlet dir = std::env::var(\"PLUGIN_DIR\").unwrap();\nlet dir = std::path::Path::new(&dir).canonicalize().unwrap(); // resolves '..', strips separators, validates\nload_plugin_function(&dir.to_str().expect(\"plugin dir must be UTF-8\"), ...);","handlingStrategy":"validation","validationCode":"const plugin_dir = \"/var/lib/influxdb3/plugins/my_plugin\";\nconst name = plugin_dir.split(\"/\").filter(Boolean).pop() ?? \"\";\nif (!name || !/^\\w[\\w.-]*$/.test(name) || name === \"..\" || name === \".\") {\n  throw new Error(`Invalid plugin directory name: '${name}'`);\n}","typeGuard":"function isValidPluginDirName(p: string): boolean {\n  const name = p.split(\"/\").filter(Boolean).pop() ?? \"\";\n  return name.length > 0 && name !== \".\" && name !== \"..\" && /^\\w[\\w.-]*$/.test(name);\n}","tryCatchPattern":null,"preventionTips":["Create plugin directories with simple ASCII names via mkdir, not scripted byte-level names.","Canonicalize/clean plugin paths before passing them to the loader.","Never pass paths with trailing slashes or '..' segments to load_plugin_function."],"tags":["python-plugins","filesystem","path","utf-8"],"backgroundTag":"invalid-argument-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"}