{"record":{"id":"3f17ed4e6afb018d","repo":"puppetlabs/puppet","slug":"failed-to-get-child-process-exit-code","errorCode":null,"errorMessage":"Failed to get child process exit code","messagePattern":"Failed to get child process exit code","errorType":"exception","errorClass":"Puppet::Util::Windows::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/windows/process.rb","lineNumber":48,"sourceCode":"    if arguments[:suppress_window]\n      create_args[:creation_flags] = CREATE_NO_WINDOW\n    end\n    if arguments[:cwd]\n      create_args[:cwd] = arguments[:cwd]\n    end\n    Process.create(create_args)\n  end\n  module_function :execute\n\n  def wait_process(handle)\n    while WaitForSingleObject(handle, WAIT_INTERVAL) == WAIT_TIMEOUT\n      sleep(0)\n    end\n\n    exit_status = -1\n    FFI::MemoryPointer.new(:dword, 1) do |exit_status_ptr|\n      if GetExitCodeProcess(handle, exit_status_ptr) == FFI::WIN32_FALSE\n        raise Puppet::Util::Windows::Error, _(\"Failed to get child process exit code\")\n      end\n\n      exit_status = exit_status_ptr.read_dword\n\n      # $CHILD_STATUS is not set when calling win32/process Process.create\n      # and since it's read-only, we can't set it. But we can execute a\n      # a shell that simply returns the desired exit status, which has the\n      # desired effect.\n      %x(#{ENV.fetch('COMSPEC', nil)} /c exit #{exit_status})\n    end\n\n    exit_status\n  end\n  module_function :wait_process\n\n  def get_current_process\n    # this pseudo-handle does not require closing per MSDN docs\n    GetCurrentProcess()","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/windows/process.rb#L30-L66","documentation":"Raised by Puppet::Util::Windows::Process.wait_process (lib/puppet/util/windows/process.rb:48) when GetExitCodeProcess returns FALSE after the wait loop. The Win32 code on Error#code is typically ERROR_INVALID_HANDLE (6) — the handle was closed or is bogus — or ERROR_ACCESS_DENIED (5) — the handle was not opened with query rights. Note the wait loop exits on anything other than WAIT_TIMEOUT, including WAIT_FAILED, so a broken wait can hand an unusable handle to GetExitCodeProcess.","triggerScenarios":"Passing a process handle that was already closed (double close, external CloseHandle, GC of the win32-process object); a handle opened without PROCESS_QUERY_INFORMATION / PROCESS_QUERY_LIMITED_INFORMATION rights; WaitForSingleObject failing so the loop falls through immediately on a bad handle.","commonSituations":"Custom exec wrappers around Puppet::Util::Windows::Process.execute that manage handles themselves; reusing a handle after wait_process already consumed it; races where the child exits during creation.","solutions":["Inspect e.code: 6 = stale/invalid handle (a handle-lifecycle bug in the caller), 5 = insufficient access (reopen with query rights).","Call wait_process exactly once with the handle obtained from Process.create / execute, and let the owning scope close it.","Never cache process handles across waits or close them from your own code.","If you control creation, keep close_handles semantics in mind so the handle is still valid at wait time."],"exampleFix":"# before — handle reused after a prior wait already consumed it\nProcess.wait_process(phandle)\nProcess.wait_process(phandle) # raises: handle stale\n\n# after — one wait per handle, straight from creation\ninfo = Process.create(command_line: cmd)\nProcess.wait_process(info.process_handle)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  exit_code = Process.wait_process(handle)\nrescue Puppet::Util::Windows::Error => e\n  Puppet.err \"wait_process failed (code #{e.code}): #{e.message}\"\n  exit_code = nil # treat outcome as unknown\nend","preventionTips":["One wait per handle; never re-wait or externally close handles","Keep creation and waiting in the same scope so handles cannot go stale","Log Error#code to distinguish handle bugs (6) from permissions (5)"],"tags":["windows","win32-api","process","handle","ffi","puppet"],"backgroundTag":"win32-process-exit-code-failed","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}