windmill-labs/windmill · error

Cannot take stdout from uv_install_proccess

Error message

Cannot take stdout from uv_install_proccess

What it means

The stdout counterpart of the stderr failure: after spawning `uv pip install`, the code calls .stdout().take() on the child to get the piped stdout handle for async draining. take() returns None when stdout was not spawned as piped or was already taken, and the code raises "Cannot take stdout from uv_install_proccess".

Source

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

                        ),
                        &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
                  )))
                  .and_then(|pid| write_file(
                      &format!("/proc/{pid}"),
                      "oom_score_adj",

View on GitHub (pinned to e474e8803c)

Solutions

  1. Ensure the uv Command sets .stdout(Stdio::piped()) (and .stderr(Stdio::piped())) at spawn time.
  2. Verify the pipe handles are taken exactly once per child; don't reuse the Child across install attempts.
  3. If uv is wrapped or the executor was patched, restore the original spawn options.
  4. Run a known-good Windmill build to confirm it's a local modification, then fix or 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

match child.stdout.take() {
  Some(p) => p,
  None => return Err(anyhow!("uv spawn must set stdout(Stdio::piped()); check executor Command construction")),
}

Prevention

When it happens

Trigger: The uv install child was spawned without Stdio::piped() for stdout, or another code path already called .stdout().take() on the same Child before this line runs.

Common situations: Executor code changes that dropped piped stdout; double-consumption of the child's stdout (e.g. a retry path reusing the same Child); a custom uv wrapper that changes how the process is spawned; Windmill version regressions.

Related errors


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