influxdata/influxdb · error · anyhow::Error

Invalid plugin directory name

Error message

Invalid plugin directory name

What it means

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.

Solutions

  1. Rename the plugin directory so its name is plain ASCII/UTF-8 and contains no '..' or trailing separators.
  2. Normalize the plugin_directory value before passing it (strip trailing '/', resolve '.'/'..' with std::path::Path::normalize/canonicalize).
  3. 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.
  4. If loading fails at startup, list the plugins directory (`ls`) to spot oddly named directories.

Example fix

// before
let dir = std::env::var("PLUGIN_DIR").unwrap(); // may contain trailing slash or non-UTF-8
load_plugin_function(&dir, ...);
// after
let dir = std::env::var("PLUGIN_DIR").unwrap();
let dir = std::path::Path::new(&dir).canonicalize().unwrap(); // resolves '..', strips separators, validates
load_plugin_function(&dir.to_str().expect("plugin dir must be UTF-8"), ...);
Defensive patterns

Strategy: validation

Validate before calling

const plugin_dir = "/var/lib/influxdb3/plugins/my_plugin";
const name = plugin_dir.split("/").filter(Boolean).pop() ?? "";
if (!name || !/^\w[\w.-]*$/.test(name) || name === ".." || name === ".") {
  throw new Error(`Invalid plugin directory name: '${name}'`);
}

Type guard

function isValidPluginDirName(p: string): boolean {
  const name = p.split("/").filter(Boolean).pop() ?? "";
  return name.length > 0 && name !== "." && name !== ".." && /^\w[\w.-]*$/.test(name);
}

Prevention

When it happens

Trigger: 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).

Common situations: 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).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of influxdata/influxdb@06200ef96b (2026-09-19). Data as JSON: /api/errors/720a7de8bad760f1. Report an issue: GitHub.

Appendix: source

Thrown at influxdb3_py_api/src/system_py.rs:333

}

/// Loads a Python function from a multi-file plugin module.
///
/// This function temporarily adds the plugin's parent directory to Python's sys.path,
/// imports the module, retrieves the function, and then cleans up sys.path.
fn load_function_from_module<'py>(
    py: Python<'py>,
    plugin_root: &Path,
    function_name: &str,
    missing_fn_error: ExecutePluginError,
) -> Result<Bound<'py, PyAny>, ExecutePluginError> {
    let parent_dir = plugin_root
        .parent()
        .ok_or_else(|| anyhow!("Plugin root has no parent directory"))?;
    let module_name = plugin_root
        .file_name()
        .and_then(|n| n.to_str())
        .ok_or_else(|| anyhow!("Invalid plugin directory name"))?;

    let sys = py.import("sys").map_err(anyhow::Error::from)?;
    let sys_path = sys.getattr("path").map_err(anyhow::Error::from)?;

    let parent_dir_str = parent_dir
        .to_str()
        .ok_or_else(|| anyhow!("Invalid UTF-8 in parent directory path"))?;

    // Acquire while attached to the Python runtime; `lock_py_attached` detaches
    // internally if the lock is contended, avoiding a GIL/lock deadlock.
    let _sys_path_guard = SYS_PATH_LOCK.lock_py_attached(py);

    // Add parent directory to sys.path
    sys_path
        .call_method1("insert", (0, parent_dir_str))
        .map_err(anyhow::Error::from)?;

    // A plugin dir added after FileFinder cached the parent is invisible until

View on GitHub (pinned to 06200ef96b)