puppetlabs/puppet · error · SystemCallError
OpenProcess
Error message
OpenProcess
What it means
The win32-process monkey patch Process.setpriority opens the target PID with OpenProcess(PROCESS_SET_INFORMATION) and raises SystemCallError (message 'OpenProcess', errno from FFI.errno) when the handle comes back NULL. It means Windows refused to give you a handle: the PID no longer exists (ERROR_INVALID_PARAMETER, 87) or you lack rights to a process of higher integrity (ERROR_ACCESS_DENIED, 5).
Source
Thrown at lib/puppet/util/windows/monkey_patches/process.rb:190
# Possible +int_priority+ values are:
#
# * Process::NORMAL_PRIORITY_CLASS
# * Process::IDLE_PRIORITY_CLASS
# * Process::HIGH_PRIORITY_CLASS
# * Process::REALTIME_PRIORITY_CLASS
# * Process::BELOW_NORMAL_PRIORITY_CLASS
# * Process::ABOVE_NORMAL_PRIORITY_CLASS
def setpriority(kind, int, int_priority)
raise TypeError unless kind.is_a?(Integer)
raise TypeError unless int.is_a?(Integer)
raise TypeError unless int_priority.is_a?(Integer)
int = Process.pid if int == 0
handle = OpenProcess(PROCESS_SET_INFORMATION, 0, int)
if handle == 0
raise SystemCallError, FFI.errno, "OpenProcess"
end
begin
result = SetPriorityClass(handle, int_priority)
raise SystemCallError, FFI.errno, "SetPriorityClass" unless result
ensure
FFI::WIN32.CloseHandle(handle)
end
0
end
private
def initialize_defaults
@hash = {
app_name: nil,
creation_flags: 0,View on GitHub (pinned to e227c27540)
Solutions
- Treat 87 as 'process gone' - verify the PID is alive immediately before calling and skip dead ones
- For 5, run the caller elevated or at the same integrity level as the target process
- Rescue SystemCallError and degrade gracefully (priority is an optimization, rarely fatal)
- Re-resolve PIDs each iteration instead of caching them
Example fix
// before
Process.setpriority(:process, cached_pid, Process::BELOW_NORMAL_PRIORITY_CLASS) # dead pid -> 87
// after
begin
Process.setpriority(:process, pid, Process::BELOW_NORMAL_PRIORITY_CLASS)
rescue SystemCallError => e
warn "priority change skipped for pid #{pid}: #{e.message}" # 87=exited, 5=access denied
end Defensive patterns
Strategy: try-catch
Validate before calling
raise ArgumentError, 'pid must be a positive Integer' unless int.is_a?(Integer) && int.positive? # existence still cannot be guaranteed without a race - keep the rescue below
Try / catch
begin
Process.setpriority(:process, pid, priority_class)
rescue SystemCallError => e
# 87 = process exited, 5 = access denied (elevated target)
warn "setpriority skipped for pid #{pid}: #{e.message}"
end Prevention
- Verify the PID is alive immediately before the call and tolerate it dying anyway
- Expect 87 for short-lived children - skip rather than fail
- Run at the same elevation as the target process when adjusting priorities
When it happens
Trigger: Process.setpriority(:process, pid, priority_class) where pid already exited (87); targeting a service/elevated process from a non-elevated agent (5); passing 0 (rewritten to Process.pid, which succeeds) vs a stale PID captured earlier; PID reuse pointing at a protected system process.
Common situations: Puppet or daemon scripts lowering priority of child processes that finish quickly; non-admin agents adjusting priority of processes started by SYSTEM; monitoring loops caching PIDs across restarts.
Related errors
- SetPriorityClass
- Failed to get child process exit code
- OpenProcess(#{desired_access.to_s(8)}, #{inherit}, #{process
- QueryFullProcessImageNameW(phandle, #{use_win32_path_format}
- RegisterEventSourceW failed to open Windows eventlog
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/d99a22e675f1c88b.
Report an issue: GitHub.