windmill-labs/windmill · error
AssignProcessToJobObject: {e}
Error message
AssignProcessToJobObject: {e} What it means
The final step of assign_job_object binds the opened child process handle to the job object via AssignProcessToJobObject. On failure the process and job handles are closed and this io::Error is returned. The classic cause is that the child is already part of another job that cannot host nested assignments (pre-Win8 semantics or explicitly configured).
Source
Thrown at backend/windmill-worker/src/common.rs:913
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))
}
}
pub fn build_command_with_isolation(program: &str, args: &[&str]) -> Command {
use tokio::process::Command;
if crate::is_unshare_enabled() {
if let Some(unshare_path) = crate::UNSHARE_PATH.as_ref() {
let mut cmd = Command::new(unshare_path);
let flags = crate::UNSHARE_ISOLATION_FLAGS.as_str();
for flag in flags.split_whitespace() {View on GitHub (pinned to e474e8803c)
Solutions
- Check the Win32 error code: ERROR_ACCESS_DENIED typically means the process is already in another job.
- Run the worker outside the parent job object (change how the service/CI wrapper launches it) or on Windows 8+ where nested jobs are supported.
- Verify the job's limit flags don't include JOB_OBJECT_LIMIT_SILENT_BREAKAWAY restrictions incompatible with assignment.
- Fallback: accept degraded cleanup (kill only the direct child pid) if nested jobs are impossible in your deployment.
Defensive patterns
Strategy: try-catch
Validate before calling
// Detect a likely parent job before spawning: workers inside CI/service job objects are prone to this failure. // Check whether the current process is already assigned to a job (QueryInformationJobObject on the enclosing job) when launching under such wrappers.
Try / catch
match assign_job_object(pid, true) {
Err(e) if e.to_string().contains("AssignProcessToJobObject") => {
log::warn!("nested job assignment denied: {e}; falling back to direct child kill");
// degrade: kill only the direct child pid on cleanup
Ok(None)
}
other => other.map(Some),
} Prevention
- Launch workers on Windows 8+ where nested job objects are supported
- Avoid wrapping the worker in a second job object (strict CI/service sandboxes)
- Allow breakaway or run the worker as a top-level process in deployments
When it happens
Trigger: assign_job_object on Windows where the child already belongs to an incompatible job object — e.g. the worker itself runs inside a job (CI runner, service wrapper) on an OS/config that disallows nested job assignment, breaking the kill-on-close isolation for the job's child processes.
Common situations: Worker running under Windows service wrappers, CI agents, or job-based sandboxes (e.g. older Windows or strict job limit configuration) that forbid AssignProcessToJobObject on already-assigned processes.
Related errors
- CreateJobObjectW: {e}
- CreateToolhelp32Snapshot: {e}
- SetInformationJobObject: {e}
- OpenProcess({pid}): {e}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/95d9cc7870a62464.
Report an issue: GitHub.