pola-rs/polars · error

invalid value for POLARS_PYTHON_SCAN_RESOLVE_THREADS: {x}

Error message

invalid value for POLARS_PYTHON_SCAN_RESOLVE_THREADS: {x}

What it means

Polars reads the environment variable POLARS_PYTHON_SCAN_RESOLVE_THREADS once, when lazily initializing the Python scan-resolve thread pool, and requires it to parse as a non-zero usize. Any other value (empty string, non-numeric, 0) aborts initialization with this panic. Valid values control how many threads the scan resolution thread pool uses (default 128).

Source

Thrown at crates/polars-utils/src/python_thread_pool.rs:31

    type Output = <&'py Py<PyAny> as IntoPyObject<'py>>::Output;
    type Target = <&'py Py<PyAny> as IntoPyObject<'py>>::Target;
    type Error = <&'py Py<PyAny> as IntoPyObject<'py>>::Error;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        IntoPyObject::into_pyobject(&self.0, py)
    }
}

impl PyThreadPool {
    pub fn new() -> Self {
        use std::num::NonZeroUsize;

        Python::attach(|py| {
            let num_threads =
                std::env::var("POLARS_PYTHON_SCAN_RESOLVE_THREADS").map_or(128, |x| {
                    x.parse::<NonZeroUsize>()
                        .unwrap_or_else(|_| {
                            panic!("invalid value for POLARS_PYTHON_SCAN_RESOLVE_THREADS: {x}")
                        })
                        .get()
                });

            if polars_config::config().verbose() {
                eprintln!("python scan_resolve_threadpool threads: {num_threads}")
            }

            return Self(
                py_scan_resolve_threadpool_cls(py)
                    .bind(py)
                    .call1((num_threads,))
                    .map(|x| x.unbind())
                    .unwrap(),
            );

            fn py_scan_resolve_threadpool_cls(py: Python<'_>) -> &'static Py<PyAny> {
                static CLS: PyOnceLock<Py<PyAny>> = PyOnceLock::new();

View on GitHub (pinned to 68506541d2)

Solutions

  1. Unset POLARS_PYTHON_SCAN_RESOLVE_THREADS to fall back to the default of 128.
  2. Set it to a plain positive integer, e.g. `export POLARS_PYTHON_SCAN_RESOLVE_THREADS=64`.
  3. Remove surrounding whitespace/quotes/BOM from the value in shell profiles, .env files, or k8s manifests.
  4. Ensure the value is set before the Polars process starts; if set programmatically, set it via os.environ before the first scan.

Example fix

// before
POLARS_PYTHON_SCAN_RESOLVE_THREADS=0
// after
POLARS_PYTHON_SCAN_RESOLVE_THREADS=64
Defensive patterns

Strategy: validation

Validate before calling

import os, re
v = os.environ.get("POLARS_PYTHON_SCAN_RESOLVE_THREADS")
if v is not None and not (v.strip().isdigit() and int(v) > 0):
    raise ValueError(f"POLARS_PYTHON_SCAN_RESOLVE_THREADS must be a positive integer, got {v!r}")

Type guard

def is_valid_thread_count(v: str | None) -> bool:
    return v is None or (v.strip().isdigit() and int(v) > 0)

Prevention

When it happens

Trigger: Setting POLARS_PYTHON_SCAN_RESOLVE_THREADS in the environment or process env to anything not parseable by `usize::from_str` as a NonZeroUsize — e.g. `"0"`, `""`, `"abc"`, `"12.5"`, `"1_000"` — and then executing the first Python-scan operation that initializes the pool.

Common situations: Copy-pasted config with typos, quoting artifacts from shell profiles or container orchestration env files (`THREADS=128\n`), setting 0 assuming it means 'unlimited', unit suffixes like `128k`, or locale-formatted numbers.

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 pola-rs/polars@68506541d2 (2026-09-10). Data as JSON: /api/errors/260024f2ee96f53f. Report an issue: GitHub.