Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Proc Janitor

FreeNot checked

Automatic orphan process cleanup daemon for macOS/Linux

GitHubEmbed

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 verificationstop confirms 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 set PROC_JANITOR_TARGETS)
  • Confirm before killing — interactive clean lists targets and prompts on a terminal (-y to skip, -d for a dry-run); non-TTY usage (scripts/cron) proceeds unprompted
  • Scan before cleanscan is always safe (detection only), clean is 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 with 0o700 (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

MIT

from github.com/jhlee0409/proc-janitor

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-janitor

FAQ

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

Compare 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