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 ofnode
), 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 → ✕. GracefulSIGTERM
first, auto-escalates toSIGKILL
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 aKill button.Launch at login, 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 processesappears next to it (or a colored dot — your choice). So you can tell at a glance without opening the panel. d**→ a 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, 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 thexattr
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
./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
oropen
), 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**— ⏸ /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, andPROC_PIDVNODEPATHINFO
for the working directory. Zombies (invisible toproc_pidinfo
) are caught via asysctl(KERN_PROC)
fallback. CPU % is derived by diffing cumulative CPU time between refreshes.Ports— each process's socket file descriptors are read viaPROC_PIDLISTFDS
+PROC_PIDFDSOCKETINFO
, keeping only TCP sockets inLISTEN
state — the same datalsof
surfaces, without spawning it.UI— SwiftUIMenuBarExtra
(.window
style),.accessory
activation policy so there's no Dock icon.Notifications & launch-at-login—UserNotifications
andServiceManagement
.
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 viaopen
(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 throughbuild.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.