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

  1. Inspect OSError.errno — ERROR_INVALID_HANDLE (6) means the handle was already closed; guard terminate() with `if self._handle`.
  2. Avoid calling both run_managed's ownership abort and an explicit terminate on the same job.
  3. Update process_control.py to make terminate() tolerant of an already-closed handle (return rather than raise on ERROR_INVALID_HANDLE) — open an issue.
  4. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/b75d4dc7e531bd40. Report an issue: GitHub.