Track, preserve, and clean up the background processes Claude Code's Bash tool leaves running.
A Claude Code plugin. It watches every npm run dev, next dev, vitest --watch, or long-running command Claude starts in the background, remembers which session spawned it, and gives you single-line controls to stash it, kill it, or bring it back later. When Claude crashes and leaves processes behind, shepherd cleans them automatically on the next launch.
Claude Code runs background commands through its Bash tool. When a session ends — /clear, a closed terminal, a crashed Claude — those children don't always die. On macOS they reparent to launchd and keep running: holding ports, eating RAM, accumulating across days. An orphaned next dev can grow past 8 GB.
Upstream issues tracking this: anthropics/claude-code#43944, #33947, #33979, #29011, #22978, #7069. No official fix in 2+ years. shepherd fills the gap.
/plugin marketplace add mgorkemuz/claude-code-shepherd
/plugin install shepherd@shepherd
From a local clone for development:
claude --plugin-dir /path/to/claude-code-shepherdAfter any plugin update: /reload-plugins.
Requires jq. macOS and Linux supported.
- Only tracks what Claude spawned. Uses the Bash tool's shell-snapshot signature to tell Claude-spawned processes apart from processes you started yourself. Kill commands are surgical — they can't touch things outside Claude's tree.
- Per-session grouping. Multiple Claude sessions (tmux teammates, background agents) each get their own tracked list.
kill --session <id>never spills into another session. - Orphan detection. Processes whose originating Claude is dead show up under their own block in
list. - Auto-clean on next launch. If Claude crashes (SIGKILL, terminal closed hard, OOM), the
SessionStarthook on the next Claude launch wipes orphans from the crashed session. Default on — orphans are residue by definition. Concurrent sessions are safe: their Claude pid is still alive, so they're not orphans. - Stash/unstash. Snapshot a dev server (command + cwd + safe env), kill it to free RAM, respawn later in the original cwd with one command. Like "pause" across a
/clear. - Port-conflict warnings. Before a Bash command with
next dev/vite/uvicorn, shepherd checks if the port is already held by a tracked process and nudges Claude viasystemMessage. - RAM threshold nudges. When a tracked process crosses 2 GB, shepherd emits
additionalContextso Claude can suggest stashing in-conversation.
Invoke from inside Claude with /shepherd:<name> (TAB-completes after /shep).
| Command | What it does | Default (no args) |
|---|---|---|
/shepherd:processes |
Tracked processes grouped by session, with ports, RAM, uptime, FD count, dev-server label. | Show everything (active + orphans). |
/shepherd:status |
One-line count: sessions, processes, orphans. | — |
/shepherd:stash |
Snapshot + kill background processes. Frees RAM and ports. | --current — this session's processes. |
/shepherd:stashes |
Table of saved stash snapshots. | — |
/shepherd:unstash |
Respawn a stash in its original cwd. | --latest --attach — most recent stash, track it in this session. |
/shepherd:rm |
Delete a stash without respawning. | --latest |
/shepherd:kill |
Terminate a process tree. | Kill the most recently-spawned tracked tree. |
/shepherd:cleanup |
Kill processes older than a duration or using too much RAM. | --older-than 24h --dry-run — safe preview. |
/shepherd:adopt <pid> |
Register an externally-spawned pid as tracked (previous Claude, terminal-started, etc). | pid required |
All commands accept arguments that get forwarded to the CLI. E.g. /shepherd:kill --all, /shepherd:stash --session <id>, /shepherd:cleanup --over-ram 2GB.
Switching projects without losing your dev server:
/shepherd:stash → snapshot npm run dev + kill it, port 3000 freed
/clear → new Claude conversation
… → work on something else
/shepherd:unstash → dev server respawns in the original project
After a Claude crash, next launch:
shepherd auto-cleanup: killed 2 orphan tree(s) from previous session(s):
pid 39987 next-server (v16.1.0) (session 214bd4)
pid 39990 node ... (session 214bd4)
Investigating RAM:
/shepherd:processes → see what's running, what's heavy
/shepherd:kill --session c1b264 → surgical — touches only this session
/shepherd:cleanup --over-ram 2GB → dry-run preview of fat processes
~/.claude/.shepherd/config.json — optional, defaults used when missing.
{
"version": 1,
"awareness": {
"ram_threshold_kb": 2097152,
"ram_alert_once_per_crossing": true,
"port_conflict_warn": true
},
"digest": { "enabled": false, "default_since": "7d" },
"stash": {
"env_allowlist": ["PATH", "NODE_ENV", "PORT", "NODE_OPTIONS"],
"env_allowlist_prefix": ["X_", "APP_"]
},
"kill": {
"grace_seconds": 3,
"safe_pgid_leader": false,
"auto_orphans_on_session_start": true
},
"notifications": { "macos_osascript": false },
"history": { "max_bytes": 1048576 }
}Edit with jq or by hand; no restart needed. The commonly-flipped knobs:
kill.auto_orphans_on_session_start— set tofalseif you deliberately leave processes running across Claude restarts and don't want them killed on the next launch.awareness.ram_threshold_kb— threshold for in-conversation RAM nudges. Default 2 GB.digest.enabled— set totrueto enable the weekly activity digest.
Claude Code hooks drive the event stream. The plugin registers:
- SessionStart — record session_id, cwd, and the Claude pid in
~/.claude/.shepherd/<id>.json. Ifauto_orphans_on_session_startis on, sweep orphans from previously-crashed sessions. - PreToolUse (matcher
Bash) — parse the incoming command for port-binding patterns; warn if another tracked session already holds one. - PostToolUse (matcher
Bash) — walk direct children of the Claude pid whose argv carries the shell-snapshot signature, record them + their subtree. Run the RAM-threshold check. - UserPromptSubmit — if the prompt looks like
/clear/ "start fresh", warn Claude about still-running tracked processes so it can offer to stash them. - Stop — at turn end, surface a summary of what's still running and the exact command to stop or stash it.
list intersects tracked pids with live ps output and walks descendants, so it finds children the hook never saw directly (like next-server forked by next dev).
kill sends SIGTERM to the tree deepest-first, waits up to kill.grace_seconds, then SIGKILLs survivors.
stash reads the command + cwd, captures allowlisted env vars (ps -wwE on macOS — returns {} on hardened macOS; /proc/<pid>/environ on Linux), writes a snapshot, then kills. unstash respawns via ( ... & exec env -i ... nohup bash -c ... ) inside the original cwd and re-attaches the new pid to the current session.
theQuert/cc-reaper solves a different problem: it cleans up Claude's own internal spawns (MCP servers, subagents). shepherd targets the processes you told Claude to start in the background. Safe to run side by side.
| cc-reaper | shepherd | |
|---|---|---|
| Target | Claude's MCP / subagent leaks | User's backgrounded commands |
| Detection | PGID + stream-json pattern |
Shell-snapshot signature + tracked state |
| Surgical per-session kill | No | Yes |
| Port / RAM / FD surfacing | Partial | Yes |
| Stash / resume | No | Yes |
| Auto-clean on next launch | LaunchAgent every 10 min | SessionStart hook |
Borrowed from cc-reaper with gratitude: the FD count, the TTY-filter orphan heuristic, the PGID-leader safety gate (opt-in via config.kill.safe_pgid_leader).
for t in test/test-*.sh; do $t; doneAll seven tests spawn synthetic processes against a sandbox HOME, covering detection, tree walk, stash/unstash, awareness (port parser + RAM threshold), cleanup + digest parsers, and plugin manifest validation. Regresses in ~10 seconds.
/plugin uninstall shepherd
MIT. See LICENSE.