About
Automatic orphan process cleanup daemon for macOS/Linux
README
Automatic orphan process cleanup daemon for macOS (Linux experimental)
CI Release crates.io Downloads GitHub Release License: MIT
proc-janitor detects and terminates orphaned processes that linger after their parent terminal or application exits. No more zombie Node.js instances eating up your RAM.
Why?
When you close a terminal (Ghostty, iTerm2, VS Code, etc.), child processes like Claude Code, Node.js, or MCP servers often keep running as orphans (PPID=1). Each one silently consumes 200-300MB of memory.
This happens because:
- Terminals don't always send SIGHUP when closed via Cmd+W or the window button
- macOS lacks
prctl(PR_SET_PDEATHSIG)— there's no native way to auto-kill children when the parent dies - Processes escape process groups via
setsid,disown, or background execution
You end up manually running pkill -f claude every few hours. proc-janitor automates this.
How It Works
Every 5 seconds (configurable):
1. Scan process table for PPID=1 processes
2. Match against target patterns (regex)
3. Skip whitelisted processes
4. Wait grace period (default 30s) to avoid false positives
5. Send SIGTERM → wait → SIGKILL if unresponsive
6. Log everything
Installation
Quick Install (Recommended)
curl -fsSL https://raw.githubusercontent.com/jhlee0409/proc-janitor/main/scripts/install-binary.sh | sh
Downloads the latest pre-built binary for your platform (macOS/Linux, x86_64/ARM64) and installs it to /usr/local/bin.
Homebrew (macOS/Linux)
brew install jhlee0409/tap/proc-janitor
# Start as a background service
brew services start proc-janitor
From crates.io
cargo install proc-janitor
GitHub Releases
Pre-built binaries for every release are available on the Releases page. Download the archive for your platform, extract, and place proc-janitor in your PATH.
| Platform | Architecture | Download |
|---|---|---|
| macOS | Apple Silicon (M1/M2/M3/M4) | proc-janitor-v*-aarch64-apple-darwin.tar.gz |
| macOS | Intel | proc-janitor-v*-x86_64-apple-darwin.tar.gz |
| Linux | x86_64 | proc-janitor-v*-x86_64-unknown-linux-gnu.tar.gz |
| Linux | ARM64 | proc-janitor-v*-aarch64-unknown-linux-gnu.tar.gz |
Build from Source
git clone https://github.com/jhlee0409/proc-janitor.git
cd proc-janitor
cargo build --release
sudo cp target/release/proc-janitor /usr/local/bin/
macOS Full Setup (LaunchAgent)
git clone https://github.com/jhlee0409/proc-janitor.git
cd proc-janitor && bash scripts/install.sh
This builds the binary, installs it, creates a default config, and sets up a macOS LaunchAgent for auto-start on login.
Linux (systemd)
cargo install proc-janitor
# Create the data/config dirs (required by the sandboxed unit before it starts)
mkdir -p ~/.proc-janitor ~/.config/proc-janitor
# Choose what to clean up — nothing is killed until you configure targets
proc-janitor config init
# Install the user service, substituting the absolute path to your binary
# (cargo install puts it in ~/.cargo/bin, which is not on systemd's PATH).
mkdir -p ~/.config/systemd/user
sed "s|__BIN__|$(command -v proc-janitor)|" resources/proc-janitor.service \
> ~/.config/systemd/user/proc-janitor.service
systemctl --user daemon-reload
systemctl --user enable --now proc-janitor
# IMPORTANT: keep the daemon running across reboots and without an active
# login session — without this, a --user service stops at logout/reboot.
loginctl enable-linger "$USER"
Uninstall
# If installed via install.sh
bash scripts/install.sh --uninstall
# If installed via Homebrew
brew uninstall proc-janitor
Quick Start
# Create a config file with explanations
proc-janitor config init
# Detect orphaned processes (safe, no killing)
proc-janitor scan
# Watch mode: continuously scan every 5 seconds
proc-janitor scan --watch 5
# Preview what would be killed (no signals sent)
proc-janitor clean --dry-run
# Kill all detected orphans (asks for confirmation on a terminal)
proc-janitor clean
# Kill only specific PIDs
proc-janitor clean --pid 12345 67890
# Kill only orphans matching a pattern
proc-janitor clean --pattern "node.*mcp"
# Kill only orphans older than 5 minutes
proc-janitor clean --min-age 300
# Interactive mode: confirm each kill
proc-janitor clean --interactive
# Start the daemon (runs in background)
proc-janitor start
# Check status
proc-janitor status
# Stop the daemon
proc-janitor stop
# Restart the daemon (stop + start)
proc-janitor restart
# Reload config without restart (send SIGHUP)
proc-janitor reload
# View cleanup statistics (last 7 days)
proc-janitor stats
proc-janitor stats --days 30
# View process tree filtered by pattern
proc-janitor tree --pattern "node"
# Diagnose issues
proc-janitor doctor
# Generate shell completions (add to your .zshrc/.bashrc)
proc-janitor completions zsh > ~/.zfunc/_proc-janitor
# Get JSON output
proc-janitor -j status
proc-janitor -j config show
proc-janitor -j scan
Configuration
Config file: ~/.config/proc-janitor/config.toml (all platforms)
# How often to scan (seconds, 1–3600)
scan_interval = 5
# Wait time before killing a new orphan (seconds)
grace_period = 30
# Time to wait after SIGTERM before SIGKILL (seconds)
sigterm_timeout = 5
# Target process patterns (regex)
targets = [
"node.*claude", # Claude Code
"claude", # Claude CLI
"node.*mcp", # MCP servers
]
# Never kill these (regex)
whitelist = [
"node.*server", # Your web servers
"pm2", # Process managers
]
[logging]
enabled = true
path = "/Users/you/.proc-janitor/logs" # absolute path required (~ not expanded)
retention_days = 7
Edit with: proc-janitor config edit
Environment Variable Overrides
Every config option can be overridden via environment variables. Values outside the valid range are rejected with a warning and the default is kept.
| Variable | Valid Range | Example |
|---|---|---|
PROC_JANITOR_SCAN_INTERVAL |
1–3600 | 10 |
PROC_JANITOR_GRACE_PERIOD |
0–3600 | 60 |
PROC_JANITOR_SIGTERM_TIMEOUT |
1–60 | 15 |
PROC_JANITOR_TARGETS |
comma-separated regexes | "python.*test,node.*dev" |
PROC_JANITOR_WHITELIST |
comma-separated regexes | "safe1,safe2" |
PROC_JANITOR_LOG_ENABLED |
true / false |
false |
PROC_JANITOR_LOG_PATH |
path under $HOME |
"/Users/you/.proc-janitor/logs" |
PROC_JANITOR_LOG_RETENTION_DAYS |
0–365 | 14 |
PROC_JANITOR_LOG_PATH is validated for safety: directory traversal (..), system paths (/etc/, /usr/, etc.), and paths outside $HOME are rejected. /var/log/ is allowed as a standard log location.
CLI Reference
Global Options
| Option | Short | Description |
|---|---|---|
--json |
-j |
Output results in JSON format (supported by: status, config show, scan, clean, stats) |
--quiet |
-q |
Suppress non-essential output (hints, spinners). Useful for scripts and cron jobs. |
Core Commands
| Command | Description |
|---|---|
start [-f|--foreground] [-d|--dry-run] |
Start the daemon. With --dry-run, scan and log without killing. |
stop |
Stop the daemon |
status |
Show daemon status (systemctl-style with uptime) |
scan [-w|--watch SECS] |
Detect orphaned processes (safe, no killing). With --watch, continuously scan at interval. |
clean [--pid PIDs] [--pattern REGEX] [-i|--interactive] [-d|--dry-run] [-y|--yes] [--min-age SECS] |
Kill orphaned target processes (all by default, or filter by PID/pattern/age). On an interactive terminal it lists the targets and asks for confirmation first; -y skips the prompt, -i confirms each kill, -d only shows what would be killed. |
restart [-f|--foreground] [-d|--dry-run] |
Restart the daemon (stop + start) |
reload |
Reload daemon configuration (sends SIGHUP, no restart needed) |
stats [--days N] |
Show cleanup statistics from the last N days (default: 7). Supports --json. |
tree [-t|--targets-only] [-m|--pattern REGEX] |
Visualize process tree (optionally filter by regex pattern) |
logs [-f|--follow] [-n N] |
View logs (N: 1–10000, default 50) |
version |
Show version and build information |
doctor |
Diagnose common issues and check system health |
completions <shell> |
Generate shell completions (bash, zsh, fish, powershell) |
Config Commands
| Command | Description |
|---|---|
config init [--force] [--preset NAME] [-y|--yes] |
Create config (auto-detects orphans, or use preset: claude, dev, minimal). Use --yes to skip prompts. --list-presets to see available presets. |
config show |
Display current config |
config edit |
Edit config in $EDITOR (validates after save, supports flags like code --wait) |
config env |
Show all environment variable overrides with current values |
config validate |
Validate configuration file and show a summary |
Session Commands
Track related processes as a group. Each tracked PID stores its start_time for PID reuse detection — session cleanup verifies process identity before sending signals, even hours after registration.
proc-janitor session register --name "my-session" --source terminal
proc-janitor session register --id custom-id --name "dev" --source vscode --parent-pid 1234
proc-janitor session track <session-id> <pid>
proc-janitor session list
proc-janitor session clean <session-id> [--dry-run]
proc-janitor session unregister <session-id>
proc-janitor session auto-clean [--dry-run]
Supported --source values: claude-code, terminal, vscode, tmux, or any custom string.
Daemon Features
Config Auto-Reload
The daemon watches config.toml for changes and automatically reloads when the file is modified. No restart needed — just edit and save.
Desktop Notifications (macOS)
When the daemon kills orphaned processes, it sends a macOS notification via Notification Center showing the count and process names.
Cleanup Statistics
Every cleanup action is recorded to ~/.proc-janitor/stats.jsonl as append-only JSON Lines. Each entry includes a timestamp, the number of processes cleaned, and details of each kill (PID, name, signal used, success/failure). The file is automatically rotated (to stats.jsonl.old) when it exceeds 5 MB.
macOS LaunchAgent
Auto-start on login:
# Install (done automatically by install.sh)
launchctl load ~/Library/LaunchAgents/com.proc-janitor.plist
# Uninstall
launchctl unload ~/Library/LaunchAgents/com.proc-janitor.plist
Safety
- Whitelist protection — matching processes are never killed
- System PID guard — PIDs 0, 1, 2 are always protected
- Grace period — orphans get time to self-cleanup before termination
- PID reuse mitigation — verifies process identity (start_time) before sending signals, including session-tracked PIDs
- Daemon identity verification —
stopconfirms the PID file points to an actual proc-janitor process before sending signals - Symlink protection — refuses to write to symlinks at predictable paths (
~/.proc-janitor/), preventing local symlink attacks - TOCTOU-safe session store — exclusive file lock held across full read-modify-write cycle
- Guided setup — shows a helpful hint when no target patterns are configured, guiding users to
config init - Safe by default — with no config file, targets are empty and nothing is killed; the daemon refuses to start until you run
config init(or setPROC_JANITOR_TARGETS) - Confirm before killing — interactive
cleanlists targets and prompts on a terminal (-yto skip,-dfor a dry-run); non-TTY usage (scripts/cron) proceeds unprompted - Scan before clean —
scanis always safe (detection only),cleanis always destructive (with optional filters) - Atomic file operations — config and session data use file locking with fsync for crash safety
- Directory permissions —
~/.proc-janitor/created with0o700(owner-only access) - Audit logging — every action is logged with timestamps
Architecture
proc-janitor/
├── src/
│ ├── main.rs # Entry point
│ ├── cli.rs # CLI argument parsing (clap)
│ ├── daemon.rs # Daemon lifecycle (start/stop/status)
│ ├── scanner.rs # Orphan process detection
│ ├── cleaner.rs # Process termination (SIGTERM/SIGKILL)
│ ├── kill.rs # Shared kill logic (system PID guard, PID reuse check, polling)
│ ├── doctor.rs # Health checks and diagnostics (8 checks)
│ ├── config.rs # TOML config + env var overrides + presets
│ ├── config_template.toml # Commented config template (embedded at compile time)
│ ├── logger.rs # Structured logging with rotation
│ ├── session.rs # Session-based process tracking (TrackedPid with start_time)
│ ├── util.rs # Shared utilities (color detection, symlink protection)
│ └── visualize.rs # ASCII process tree
├── resources/
│ └── com.proc-janitor.plist # LaunchAgent template
├── scripts/
│ └── install.sh # One-line installer
├── tests/
│ └── integration_test.rs
├── Cargo.toml
└── LICENSE
License
Installing Proc Janitor
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/jhlee0409/proc-janitorFAQ
Is Proc Janitor MCP free?
Yes, Proc Janitor MCP is free — one-click install via Unyly at no cost.
Does Proc Janitor need an API key?
No, Proc Janitor runs without API keys or environment variables.
Is Proc Janitor hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Proc Janitor in Claude Desktop, Claude Code or Cursor?
Open Proc Janitor on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Proc Janitor with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
