windmill-labs/windmill · error
OpenProcess({pid}): {e}
Error message
OpenProcess({pid}): {e} What it means
assign_job_object opens the child process by PID with PROCESS_SET_QUOTA | PROCESS_TERMINATE rights so it can be bound to the job object. If OpenProcess fails (most commonly the process already exited, or access is denied), the job handle is closed and this io::Error is returned including the pid.
Source
Thrown at backend/windmill-worker/src/common.rs:903
SetInformationJobObject(
job,
JobObjectExtendedLimitInformation,
&info as *const _ as _,
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
.map_err(|e| {
let _ = windows::Win32::Foundation::CloseHandle(job);
std::io::Error::new(
std::io::ErrorKind::Other,
format!("SetInformationJobObject: {e}"),
)
})?;
let process_handle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, pid)
.map_err(|e| {
let _ = windows::Win32::Foundation::CloseHandle(job);
std::io::Error::new(
std::io::ErrorKind::Other,
format!("OpenProcess({pid}): {e}"),
)
})?;
let assign_result = AssignProcessToJobObject(job, process_handle);
let _ = windows::Win32::Foundation::CloseHandle(process_handle);
assign_result.map_err(|e| {
let _ = windows::Win32::Foundation::CloseHandle(job);
std::io::Error::new(
std::io::ErrorKind::Other,
format!("AssignProcessToJobObject: {e}"),
)
})?;
Ok(Win32JobHandle(job))
}
}View on GitHub (pinned to e474e8803c)
Solutions
- Check the pid in the message: if the process exited, look for the child's own startup failure (missing binary/DLL, bad script) — the OpenProcess error is a symptom.
- Ensure the worker runs as the same or a more privileged user than the child; grant PROCESS_SET_QUOTA/PROCESS_TERMINATE access.
- Reduce time-to-assign or handle spawn failures earlier so dead pids are not passed to assign_job_object.
- Retry the job if it was a transient crash.
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check: confirm the child is still alive before assigning it to the job object
fn process_alive(pid: u32) -> bool {
std::process::Command::new("tasklist")
.args(["/FI", &format!("PID eq {pid}"), "/NH"])
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).contains(&pid.to_string()))
.unwrap_or(false)
} Try / catch
match assign_job_object(pid, true) {
Err(e) if e.to_string().contains(&format!("OpenProcess({pid})")) => {
// child already exited — treat as failed spawn, surface child startup logs
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("child {pid} exited before job assignment: {e}"),
))
}
other => other,
} Prevention
- Diagnose why the child exits instantly (missing interpreter, DLL, bad script) — OpenProcess failure is a symptom
- Run the worker with an account that can open its children with PROCESS_SET_QUOTA | PROCESS_TERMINATE
- Assign the job object as early as possible after spawning the suspended child
When it happens
Trigger: assign_job_object called with a pid whose process has already terminated (race between spawn suspension and job assignment) or which the worker account cannot open with the required access rights.
Common situations: Child process crashing instantly on startup (bad interpreter, missing DLL) so the pid is dead by assignment time; protected processes; running the worker under a service account lacking rights to the child.
Related errors
- CreateToolhelp32Snapshot: {e}
- CreateJobObjectW: {e}
- AssignProcessToJobObject: {e}
- SetInformationJobObject: {e}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/1ee51b97b4390a54.
Report an issue: GitHub.