File-New-Project/EarTrumpet · error · ZombieProcessException
Process is a zombie: {processId}
Error message
Process is a zombie: {processId} What it means
ZombieProcessException is both the exception type and the detector. ThrowIfZombie opens a process handle and calls Kernel32.WaitForSingleObject(handle, 0): a non-WAIT_TIMEOUT return means the handle is already signaled, i.e. the process has terminated. It then throws. Both DesktopAppInfo and ModernAppInfo call this right after a successful OpenProcess to catch the race where the process died between session creation and info construction.
Source
Thrown at EarTrumpet/DataModel/AppInformation/Internal/ZombieProcessException.cs:14
using EarTrumpet.Interop;
using System;
namespace EarTrumpet.DataModel.AppInformation.Internal
{
public class ZombieProcessException : Exception
{
public ZombieProcessException(int processId) : base($"Process is a zombie: {processId}") { }
public static void ThrowIfZombie(int processId, IntPtr handle)
{
if (Kernel32.WaitForSingleObject(handle, 0) != Kernel32.WAIT_TIMEOUT)
{
throw new ZombieProcessException(processId);
}
}
}
}
View on GitHub (pinned to aa894e51c2)
Solutions
- Catch ZombieProcessException wherever you construct DesktopAppInfo / ModernAppInfo and treat the session as expired.
- If you call ThrowIfZombie yourself, ensure the caller can tolerate the throw and clean up the handle (it is in a try/finally that closes the handle).
- Do not widen the WaitForSingleObject timeout to mask the issue; instead discard the dead session.
- Log the PID for diagnostics, then suppress the session from the UI.
Example fix
// before
using (var h = Kernel32.OpenProcess(flags, false, pid))
{
ZombieProcessException.ThrowIfZombie(pid, h);
ReadInfo(h);
}
// after
try { using (var h = Kernel32.OpenProcess(flags, false, pid)) { ZombieProcessException.ThrowIfZombie(pid, h); ReadInfo(h); } }
catch (ZombieProcessException) { /* process died mid-read */ } Defensive patterns
Strategy: try-catch
Validate before calling
// if you call ThrowIfZombie yourself, check first to avoid the throw bool IsSignaled(IntPtr handle) => Kernel32.WaitForSingleObject(handle, 0) != Kernel32.WAIT_TIMEOUT;
Try / catch
try { ZombieProcessException.ThrowIfZombie(pid, handle); /* read info */ } catch (ZombieProcessException) { /* process already terminated; abort info read */ } Prevention
- Always pair OpenProcess success with a ThrowIfZombie-or-handle-signaled check.
- Keep the handle close in a try/finally so it is not leaked on the throw.
- Discard the session, do not retry — a signaled handle means the process is gone.
When it happens
Trigger: A process handle was opened successfully, but by the time ThrowIfZombie runs the process has exited, so WaitForSingleObject returns WAIT_OBJECT_0 (signaled) instead of WAIT_TIMEOUT, and the exception is thrown.
Common situations: Process exits during the window between OpenProcess and the first info query; teardown race when a device/app is closing; rapid create/destroy of audio processes (notifications, system sounds).
Related errors
- Process is a zombie: {processId}
- Process is a zombie: {processId}
- Device session parent is invalid but device is still notifyi
AI-assisted analysis of File-New-Project/EarTrumpet@aa894e51c2 (2026-08-13).
Data as JSON: /api/errors/22d503d8d54648d8.
Report an issue: GitHub.