abhigyanpatwari/GitNexus · error · OSError
TerminateJobObject failed
Error message
TerminateJobObject failed
What it means
_WindowsJob.terminate() calls TerminateJobObject(handle, exit_code=1); on FALSE it raises OSError(get_last_error(), ...). Termination is best-effort cleanup during abort/timeout, so failure here means the job handle is stale or the kernel rejected the call.
Source
Thrown at eval/workflow_bench/process_control.py:282
process_handle = wintypes.HANDLE(int(process._handle)) # type: ignore[attr-defined]
if not kernel32.AssignProcessToJobObject(handle, process_handle):
raise OSError(ctypes.get_last_error(), "AssignProcessToJobObject failed")
status = int(ntdll.NtResumeProcess(process_handle))
if status != 0:
raise OSError(status, "NtResumeProcess failed")
except BaseException:
# The child is still suspended when assignment fails. Kill it
# before releasing any handle; never retry with job breakaway.
process.kill()
process.wait()
self.close()
raise
def terminate(self) -> None:
import ctypes
if self._handle and not self._kernel32.TerminateJobObject(self._handle, 1):
raise OSError(ctypes.get_last_error(), "TerminateJobObject failed")
def active_processes(self) -> int:
"""Return live members so closing the job cannot hide forced cleanup."""
import ctypes
from ctypes import wintypes
class JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(ctypes.Structure):
_fields_ = [
("TotalUserTime", ctypes.c_longlong),
("TotalKernelTime", ctypes.c_longlong),
("ThisPeriodTotalUserTime", ctypes.c_longlong),
("ThisPeriodTotalKernelTime", ctypes.c_longlong),
("TotalPageFaultCount", wintypes.DWORD),
("TotalProcesses", wintypes.DWORD),
("ActiveProcesses", wintypes.DWORD),
("TotalTerminatedProcesses", wintypes.DWORD),
]View on GitHub (pinned to d540b00184)
Solutions
- Inspect OSError.errno — ERROR_INVALID_HANDLE (6) means the handle was already closed; guard terminate() with `if self._handle`.
- Avoid calling both run_managed's ownership abort and an explicit terminate on the same job.
- Update process_control.py to make terminate() tolerant of an already-closed handle (return rather than raise on ERROR_INVALID_HANDLE) — open an issue.
- Run on Linux to bypass the Windows job path.
Defensive patterns
Strategy: try-catch
Try / catch
try:
job.terminate()
except OSError as exc:
if sys.platform == 'win32' and exc.winerror == 6: # ERROR_INVALID_HANDLE
log.debug('job already closed; nothing to terminate')
else:
raise Prevention
- Do not call terminate() after close().
- Avoid overlapping timeout-abort and explicit terminate() on the same job.
- Track job lifecycle in the caller so cleanup runs once.
When it happens
Trigger: On Windows during timeout/cleanup when TerminateJobObject returns FALSE: typically self._handle was already closed by a prior close(), or the OS denied termination (rare).
Common situations: Double-terminate (close() then terminate()) due to concurrent timeout and ownership-slot abort; handle reuse race; security software blocking termination.
Related errors
- CreateJobObjectW failed
- SetInformationJobObject failed
- AssignProcessToJobObject failed
- QueryInformationJobObject failed
- NtResumeProcess failed
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/b75d4dc7e531bd40.
Report an issue: GitHub.