argoproj/argo-workflows · error

failed to load CtrlRoutine

Error message

failed to load CtrlRoutine: %w

What it means

After opening the target process on Windows, signalProcess must locate the CtrlRoutine (the console control-handler entry point in kernelbase.dll) inside the remote process so it can inject a thread that delivers the signal. procCtrlRoutine.Find() failed, so the Windows signal cannot be delivered via remote thread.

Solutions

  1. Verify the Windows version supports the CtrlRoutine lookup used by this hcsshim-derived code (upgrade host/OS image if very old)
  2. Check that the target is a normal console process with kernelbase.dll loaded, not a PPL/protected process
  3. Inspect argoexec logs for the wrapped cause; if module enumeration is blocked by container isolation, run with adjusted container privileges
  4. As a fallback, terminate the process group via job-object termination instead of remote-thread signaling
Defensive patterns

Strategy: fallback

Validate before calling

// verify target module enumeration is possible before signaling
if h, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_TERMINATE, true, pid); err != nil {
    return err
} else { windows.Close(h) }

Try / catch

if err := osspecific.Kill(pid); err != nil && strings.Contains(err.Error(), "failed to load CtrlRoutine") {
    // fall back to hard termination (job-object/TerminateProcess)
}

Prevention

When it happens

Trigger: Kill() → signalProcess() on Windows when the CtrlRoutine address cannot be resolved in the target process — module snapshot/enumeration failure, kernelbase.dll not loaded in the target, or incompatible Windows version where the export layout differs.

Common situations: Targets running in job containers where module enumeration of the remote process is restricted; Windows version mismatch between the resolving code's assumptions and the target OS build; hardened/protected processes refusing cross-process module inspection.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/80c08c4f0a1d5e97. Report an issue: GitHub.

Appendix: source

Thrown at workflow/executor/osspecific/signal_windows.go:76

	return err
}

// signalProcess sends the specified signal to a process.
//
// Code +/- copied from: https://github.com/microsoft/hcsshim/blob/1d69a9c658655b77dd4e5275bff99caad6b38416/internal/jobcontainers/process.go#L251
// License: MIT
// Author: Microsoft
func signalProcess(pid uint32, signal int) error {
	hProc, err := windows.OpenProcess(windows.PROCESS_TERMINATE, true, pid)
	if err != nil {
		return fmt.Errorf("failed to open process: %w", err)
	}
	defer func() {
		_ = windows.Close(hProc)
	}()

	if err := procCtrlRoutine.Find(); err != nil {
		return fmt.Errorf("failed to load CtrlRoutine: %w", err)
	}

	threadHandle, err := createRemoteThread(hProc, nil, 0, procCtrlRoutine.Addr(), uintptr(signal), 0, nil)
	if err != nil {
		return fmt.Errorf("failed to open remote thread in target process %d: %w", pid, err)
	}
	defer func() {
		_ = windows.Close(windows.Handle(threadHandle))
	}()
	return nil
}

// Following code has been generated using github.com/Microsoft/go-winio/tools/mkwinsyscall and inlined
// for easier usage

// HANDLE CreateRemoteThread(
//
//	HANDLE                 hProcess,

View on GitHub (pinned to 35bff19146)