proc-janitor 0.7.0

Automatic orphan process cleaner daemon for macOS/Linux
proc-janitor-0.7.0 is not a library.

proc-janitor

Automatic orphan process cleanup daemon for macOS (Linux experimental)

CI crates.io Downloads License: MIT Rust

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

From crates.io

cargo install proc-janitor

Build from Source

git clone https://github.com/jhlee0409/proc-janitor.git
cd proc-janitor
cargo build --release

# Copy binary to PATH
sudo cp target/release/proc-janitor /usr/local/bin/

One-Line Install (macOS)

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.

Homebrew (macOS)

brew tap jhlee0409/proc-janitor
brew install proc-janitor
brew services start proc-janitor

Uninstall

# Via install script
bash scripts/uninstall.sh

# Or via Homebrew
brew uninstall proc-janitor

Linux (systemd)

cargo install proc-janitor
sudo cp resources/proc-janitor.service /etc/systemd/user/
systemctl --user enable --now 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

# Kill all detected orphans
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"

# 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

# 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)
--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] Kill orphaned target processes (all by default, or filter by PID/pattern). With -i, confirm each kill.
tree [-t|--targets-only] Visualize process tree
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
  • 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