influxdata/influxdb · error

plugin code contains null bytes

Error message

plugin code contains null bytes

What it means

Single-file plugins (no plugin_root) are executed by passing their source to py.run after converting it with CString::new, and C strings cannot contain NUL bytes. If the stored plugin code contains a single '\0', CString::new fails and the error is wrapped with context 'plugin code contains null bytes'. In practice this means the uploaded 'source' is not text at all — it is binary, corrupt, or wrongly encoded.

Source

Thrown at influxdb3_py_api/src/system_py.rs:296

/// For multi-file plugins (when `plugin_root` is `Some`), imports the module and retrieves
/// the function. For single-file plugins, executes the code in an isolated namespace and
/// retrieves the function.
fn load_plugin_function<'py>(
    py: Python<'py>,
    code: &str,
    plugin_root: Option<&Path>,
    call_site: &str,
    missing_fn_error: ExecutePluginError,
) -> Result<Bound<'py, PyAny>, ExecutePluginError> {
    if let Some(root_path) = plugin_root {
        load_function_from_module(py, root_path, call_site, missing_fn_error)
    } else {
        // Create isolated globals for this plugin execution. Without this, single-file
        // plugins would share __main__'s namespace and could overwrite each other's
        // function definitions during concurrent execution.
        let globals = PyDict::new(py);
        let code = CString::new(code)
            .map_err(|e| anyhow::Error::new(e).context("plugin code contains null bytes"))?;
        let call_site = CString::new(call_site)
            .map_err(|e| anyhow::Error::new(e).context("call site contains null bytes"))?;
        py.run(&code, Some(&globals), None)
            .map_err(anyhow::Error::from)?;
        py.eval(&call_site, Some(&globals), None)
            .map_err(|_| missing_fn_error)
    }
}

/// Serializes temporary `sys.path` mutation during multi-file plugin imports.
static SYS_PATH_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));

/// True when `err` is a `ModuleNotFoundError` for `module_name` itself, so no
/// plugin code has run and re-importing is side-effect free.
fn is_module_not_found(py: Python<'_>, err: &PyErr, module_name: &str) -> bool {
    err.is_instance_of::<PyModuleNotFoundError>(py)
        && err
            .value(py)

View on GitHub (pinned to d28e26e048)

Solutions

  1. Re-save the plugin file as UTF-8 (no BOM) and re-upload
  2. Verify before upload: the file contains no NUL bytes (check b'\x00' in data)
  3. If you meant to ship compiled code, don't — the engine executes plain .py source; multi-file plugin directories are the packaging mechanism

Example fix

# before: plugin.py saved as UTF-16 / binary -> 'plugin code contains null bytes'

# after: verify plain UTF-8 text before upload
src = open("plugin.py", "rb").read()
assert b"\x00" not in src, "not valid UTF-8 text"
upload(plugin="plugin.py")
Defensive patterns

Strategy: validation

Validate before calling

src = open("plugin.py", "rb").read()
assert b"\x00" not in src, "plugin file contains NUL bytes — re-save as UTF-8 text"
src.decode("utf-8")  # must succeed
# only then upload

Prevention

When it happens

Trigger: Uploading a compiled/binary artifact (e.g. a .pyc) or a file saved as UTF-16 (whose byte stream contains NULs) as single-file plugin code; a truncated or binary-corrupted transfer leaving stray NUL bytes in the .py file.

Common situations: Editor or pipeline re-saving the plugin with UTF-16 encoding; build steps that publish compiled artifacts; copy-paste through tools that mangle encoding; accidental upload of a zip/gz file.

Related errors


AI-assisted analysis of influxdata/influxdb@d28e26e048 (2026-08-16). Data as JSON: /api/errors/6510aef825152816. Report an issue: GitHub.