influxdata/influxdb · error · anyhow::Error

Invalid UTF-8 in parent directory path

Error message

Invalid UTF-8 in parent directory path

What it means

Thrown by load_function_from_module when the parent directory of the plugin root (the directory that must be prepended to sys.path so the plugin module is importable) is not valid UTF-8. Python's sys.path entries must be strings, so a non-UTF-8 OsStr cannot be converted with to_str().

Solutions

  1. Rename the offending ancestor directory of the plugin path so the entire path is valid UTF-8.
  2. Check the filesystem encoding/locale the process runs under (`locale`); run with a UTF-8 locale (LC_ALL=C.UTF-8) so paths encode cleanly.
  3. Validate the plugin directory path with `Path::to_str().is_some()` before calling the API and fail fast with a clear message.
  4. Avoid symlinking plugin dirs through paths with exotic names; place plugins under a simple ASCII path like /var/lib/influxdb3/plugins.
Defensive patterns

Strategy: validation

Validate before calling

const path = process.env.INFLUXDB3_PLUGIN_DIR ?? "";
const encoder = new TextEncoder();
// Node keeps UTF-8 internally; validate round-trip of raw bytes from the FS before use
if (!path || Buffer.compare(Buffer.from(path, "utf8"), encoder.encode(path)) !== 0) {
  throw new Error(`Plugin dir path '${path}' is not valid UTF-8`);
}

Prevention

When it happens

Trigger: load_plugin_function invoked with a plugin_directory whose PARENT path component contains non-UTF-8 bytes (e.g. a directory name with raw bytes or invalid encoding), preventing `parent_dir.to_str()` from succeeding.

Common situations: InfluxDB installed or plugins stored under a path containing characters produced by a non-UTF-8 filesystem encoding or pasted control characters; running under a locale like LC_ALL=C/POSIX where user-created paths use surrogate bytes; symlinks or mounts with unusual names.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


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

Appendix: source

Thrown at influxdb3_py_api/src/system_py.rs:340

    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
    // import caches are invalidated; retry once.
    let import_result = match py.import(module_name) {
        Err(e) if is_module_not_found(py, &e, module_name) => {
            let _ = py
                .import("importlib")
                .and_then(|m| m.call_method0("invalidate_caches"));
            py.import(module_name)

View on GitHub (pinned to 06200ef96b)