# Dead Process Mate – a menu-bar app to kill leftover Node/dev processes

> Source: <https://github.com/BartosK97/dead-process-mate>
> Published: 2026-07-12 18:02:12+00:00

**A tiny native macOS menu-bar app that keeps an eye on your Node/dev processes,
shows which ports they hold, and helps you kill the leftover ones in one click.**

No Electron. No `ps`

/`lsof`

shelling out. Just a small Swift binary reading the
kernel directly. Lives only in the menu bar — no Dock icon.

When you code — especially with AI assistants — dev servers pile up fast. A
`vite`

here, a `next dev`

there, three `pnpm`

watchers, a script you `Ctrl-C`

'd
that didn't actually die. They keep holding ports (`:3000`

is “already in use”
again), eating RAM, and spinning the fans. Dead Process Mate makes all of that
visible in your menu bar and one click away from gone.

It's especially good at spotting the **dead leftovers**: a dev server whose
parent terminal is gone (it got reparented to `launchd`

), something that's been
idle for hours, or a zombie — exactly the stuff that quietly wastes your
machine.

**Process list**— your dev processes with friendly names (`vite dev`

,`next-server`

,`npm run dev`

— not a wall of`node`

), CPU %, memory, uptime, and the project folder each runs in. Search, sort, and group by project.**Ports view**— what's listening where, mapped to the owning process. Common dev ports (`3000`

,`5173`

,`8080`

, …) are highlighted so the server you're looking for pops.**Health at a glance**— a colored dot per process (see the table below), and a matching indicator right in the menu bar.** One-click kill**— hover any row → ✕. Graceful`SIGTERM`

first, auto-escalates to`SIGKILL`

after 3 s. Right-click for Force Kill and copy/reveal actions. “Kill N” in the footer clears every flagged process at once.**Notifications**— optional, throttled alerts for orphaned processes, idle servers still holding a port, memory hogs, and “too many idle Node processes piling up.” Every banner has a**Kill** button.**Launch at login**, pause monitoring, configurable thresholds, force-kill toggle.

Everything is local — it never makes a network connection.

Each process gets a status; the worst one bubbles up to the menu bar. Defaults
are all editable in **Settings → Thresholds**.

| Dot | Status | When |
|---|---|---|
| 🟢 | Healthy |
running normally |
| 🟡 | Idle / old |
running ≥ 4 h, or idle (<1% CPU) for ≥ 30 min, or over the memory limit |
| 🟠 | High CPU |
≥ 80% CPU sustained for ≥ 15 s |
| 🔴 | Orphaned |
parent process is gone (reparented to `launchd` ) — a classic abandoned dev server |
| 🔴 | Zombie |
defunct process (kill its parent) |

Precedence: zombie › orphaned › high-CPU › idle/old › healthy.

**All healthy**→ a calm monochrome icon (choose CPU / activity pulse / gauge / stack in Settings).** Something's wrong**→ the icon tints and a** count of flagged processes**appears next to it (or a colored dot — your choice). So you can tell at a glance without opening the panel.** Paused**→ a pause glyph.

**Requirements:** macOS 14+ · Apple Silicon.

```
brew install --cask --no-quarantine bartosk97/tap/dead-process-mate
```

On Homebrew 6+ you'll be asked to trust the tap once — run the
`brew trust bartosk97/tap`

command it prints, then re-run the install.

Grab the `.dmg`

from the [latest release](https://github.com/BartosK97/dead-process-mate/releases/latest),
drag the app to Applications, then clear the download quarantine and open it:

```
xattr -dr com.apple.quarantine /Applications/DeadProcessMate.app
open /Applications/DeadProcessMate.app
```

This build is

ad-hoc signed, not notarized(no paid Apple account yet), so macOS quarantines the download — that's what the`--no-quarantine`

flag and the`xattr`

command handle. Everything runs locally; the app makes no network calls.

Needs the Swift toolchain from Xcode **or** the Command Line Tools
(`xcode-select --install`

) — a full Xcode install is **not** required.

```
git clone https://github.com/BartosK97/dead-process-mate.git
cd dead-process-mate

./build.sh run       # build + package + ad-hoc sign, then launch
# or
./build.sh build     # just build dist/DeadProcessMate.app
./build.sh install   # copy into /Applications and launch
```

`build.sh`

compiles with SwiftPM, assembles a `DeadProcessMate.app`

bundle
(generating the icon from `icon.png`

), and **ad-hoc code-signs** it. On Apple
Silicon a signature is mandatory just to run, and notifications need a real
bundle identity — the script handles both.

Launch the(via`.app`

`build.sh`

or`open`

), not the bare binary in`.build/`

. Running the raw executable breaks notifications (no bundle identity).

macOS asks to allow **notifications** — click **Allow** (or enable it later in
System Settings › Notifications › Dead Process Mate). If you skip it, everything
else still works; you just won't get banners.

**Click the menu-bar icon** to open the panel.**Processes tab**— hover a row to reveal the**✕ kill** button. Click a row to expand it (PID/PPID, full command, folder, ports, copy/reveal). Right-click for a full menu (Kill, Force Kill, Copy PID/command, Reveal in Finder).**Ports tab**— see every listening port and who owns it; hover → ✕ to free it.** Footer**— ⏸ pause/resume,** Kill N**(kill everything flagged), ⚙ settings, ⏻ quit.

Open with the ⚙ in the footer (or ⌘,). Four tabs:

**General**— menu-bar icon, warning indicator (count / dot / none), refresh interval (2 / 5 / 10 / 30 s), launch at login.** Thresholds**— when a process turns yellow/orange/red and when it notifies: “old after N h”, “idle after N min”, high-CPU %, high-memory, and the “too many idle” count.**Notifications**— master toggle, per-event toggles, and a repeat throttle so you're never nagged about the same thing twice within the window.**Watched**— which process names to track (`node`

,`vite`

,`next-server`

, …), a show-all toggle, and a force-kill (SIGKILL) toggle (off by default — graceful SIGTERM first is recommended).

**Processes**—`proc_listpids`

+`proc_pidinfo`

(`PROC_PIDTBSDINFO`

/`PROC_PIDTASKINFO`

) for pid/ppid/start-time/RSS/CPU,`proc_pidpath`

+`KERN_PROCARGS2`

for the full command line, and`PROC_PIDVNODEPATHINFO`

for the working directory. Zombies (invisible to`proc_pidinfo`

) are caught via a`sysctl(KERN_PROC)`

fallback. CPU % is derived by diffing cumulative CPU time between refreshes.**Ports**— each process's socket file descriptors are read via`PROC_PIDLISTFDS`

+`PROC_PIDFDSOCKETINFO`

, keeping only TCP sockets in`LISTEN`

state — the same data`lsof`

surfaces, without spawning it.**UI**— SwiftUI`MenuBarExtra`

(`.window`

style),`.accessory`

activation policy so there's no Dock icon.**Notifications & launch-at-login**—`UserNotifications`

and`ServiceManagement`

.

A full scan of ~800 processes takes **~15 ms** and runs off the main thread, so
it's effectively free.

Kills are guarded against **PID reuse**: a process's start-time signature is
captured and re-checked before escalating to `SIGKILL`

, so a recycled PID can't
lead to killing the wrong process. All scanning is read-only kernel
introspection of **your own** processes — no root, no privileged helper.

```
Sources/
  CSystemProbe/        C shim over libproc (process + listening-socket scan)
  DeadProcessMate/
    App/               @main app, self-test hook
    Model/             data models, naming, preferences
    Core/              scanner, monitor, killer, notifications, login item
    Views/             menu-bar panel, rows, settings
build.sh               build + package + ad-hoc sign (also builds the .icns)
Info.plist             LSUIElement bundle metadata
icon.png               1024×1024 app-icon source
```

Run a headless scan (handy for hacking on the scanner):

```
DPM_SELFTEST=1 ./dist/DeadProcessMate.app/Contents/MacOS/DeadProcessMate
```

**No notifications?** They only work when the app runs from the`.app`

bundle launched via`open`

(not the bare binary), and after you approve the prompt. Re-enable in System Settings › Notifications › Dead Process Mate.**“app can't be opened” / it won't launch?** Make sure it's signed — always launch through`build.sh`

/`open`

, not`.build/release/DeadProcessMate`

.**A process I care about isn't listed?** Add its name in Settings → Watched, or turn on “Show all processes.”

Issues and PRs welcome. It's a small, single-purpose app — keep it native, keep
it light. `swift build`

to compile, `./build.sh run`

to try it.

MIT — see [LICENSE](/BartosK97/dead-process-mate/blob/main/LICENSE).
