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: {}",
&reqView on GitHub (pinned to e474e8803c)
Solutions
- Ensure the uv Command is built with .stderr(Stdio::piped()) (and .stdout(Stdio::piped())) before spawn.
- Take each pipe handle exactly once; verify no other code path consumes the Child's stderr before this point.
- If you patched the executor or wrapped uv, restore the piped-stdio spawn options.
- 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
- Always spawn subprocesses with .stdout(Stdio::piped()).stderr(Stdio::piped()) when output will be drained
- Never take() the same pipe handle twice; restructure retry logic to respawn the child
- Add a CI test that runs a minimal uv install path after executor changes
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
- Cannot take stdout from uv_install_proccess
- uv pip install was canceled
- ${what} failed:\n${output}
- failed to execute replace_ephemeral command: {}
- Cannot list python versions, is uv (0.5.19 and newer) instal
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/35dec4c16a37383b.
Report an issue: GitHub.