windmill-labs/windmill · error

Cannot take stderr from uv_install_proccess

Error message

Cannot take stderr from uv_install_proccess

What it means

handle_python_reqs spawns a `uv pip install` child process and then calls .stderr().take() on the tokio::process::Child to grab the piped stderr handle for async draining. take() returns None if the child has no stderr pipe — i.e. it was spawned without Stdio::piped() for stderr, or the handle was already taken. The code maps that None to "Cannot take stderr from uv_install_proccess".

Source

Thrown at backend/windmill-worker/src/python_executor.rs:2998

                        &job_id,
                        w_id,
                        format!(
                            "\nError while spawning proccess:\n{e}",
                        ),
                        &conn,
                    )
                    .await;
                    pids.lock().await.get_mut(i).and_then(|e| e.take());
                    return Err(Error::from(e));
                }
            };

            let (mut stderr_buf, mut stdout_buf) = Default::default();
            let (mut stderr_pipe, mut stdout_pipe) = (
                uv_install_proccess
                    .stderr()
                    .take()
                    .ok_or(anyhow!("Cannot take stderr from uv_install_proccess"))?,
                uv_install_proccess
                    .stdout()
                    .take()
                    .ok_or(anyhow!("Cannot take stdout from uv_install_proccess"))?
            );
            let (stderr_future, stdout_future) = (
                stderr_pipe.read_to_string(&mut stderr_buf),
                stdout_pipe.read_to_string(&mut stdout_buf)
            );

            if let Some(pid) = pids.lock().await.get_mut(i) {
                *pid = uv_install_proccess.id();
                #[cfg(unix)]
                if let Err(e) = uv_install_proccess
                  .id()
                  .ok_or(Error::InternalErr(format!(
                    "failed to get PID for python installation process: {}",
                    &req

View on GitHub (pinned to e474e8803c)

Solutions

  1. Ensure the uv Command is built with .stderr(Stdio::piped()) (and .stdout(Stdio::piped())) before spawn.
  2. Take each pipe handle exactly once; verify no other code path consumes the Child's stderr before this point.
  3. If you patched the executor or wrapped uv, restore the piped-stdio spawn options.
  4. Upgrade/downgrade Windmill to a version where the uv spawn options are intact, and report the regression.

Example fix

// before
let mut uv_install_proccess = Command::new("uv").args([...]).spawn()?;
// after
use std::process::Stdio;
let mut uv_install_proccess = Command::new("uv")
    .args([...])
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn()?;
Defensive patterns

Strategy: fallback

Try / catch

// worker-side: surface a clear regression message instead of a bare None
match child.stderr.take() {
  Some(p) => p,
  None => return Err(anyhow!("uv spawn must set stderr(Stdio::piped()); check executor Command construction")),
}

Prevention

When it happens

Trigger: The uv install child process was spawned without its stderr configured as piped (a code/config regression in how the Command is built), or the stderr handle was already consumed by a previous take() on the same Child.

Common situations: A Windmill version regression changing process spawn options; another code path already took the stderr pipe (double invocation); modifications to uv invocation (e.g. switching to a shell wrapper) that dropped piped stdio; uv binary replaced by a wrapper script that alters spawn behavior in custom workers.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/35dec4c16a37383b. Report an issue: GitHub.