Skip to content

Orphaned Claude Code Processes: Kill Only the Dead Ones

The usual advice works the way a grenade works. It also kills the session you are in, its subagents mid-write, and processes that merely mention a .claude path.

August 8, 202610 min readShift The Culture

You closed the terminal window. Days later Activity Monitor shows a column of claude processes with multi-gigabyte resident memory, attached to nothing. The advice you will find for this is almost always pkill -9 -f claude, and it works, in the sense that a grenade works. It also kills the session you are currently working in, the subagents it spawned mid-write, and — because of how the pattern matches — a set of processes that have nothing to do with Claude at all. Here is how to identify only the dead ones, and kill only those.

Why it happens

On Unix, when a parent process dies, its children are not killed. They are reparented to init (pid 1) and keep running. A terminal emulator normally handles this by sending SIGHUPto its foreground process group when the window closes — that is what the “HUP” in hangup means, and it is a signal from the era of actual telephone lines.

A long-running interactive CLI can miss that. If it does not handle SIGHUP, or the emulator does not send one on that particular close path (closing a window vs quitting the app vs killing the pane are three different code paths, and IDE-integrated terminals are a fourth), the process survives its parent, gets adopted by pid 1, loses its controlling terminal, and keeps its entire heap resident. Nothing is attached to it. Nothing will ever be attached to it again. It is pure resident memory until you or a reboot ends it.

The processes are not stuck, and they are not doing anything. They are waiting for input from a terminal that no longer exists.

This is a well-documented rough edge — there is a cluster of open reports on the Claude Code issue tracker covering the terminal-close path, the desktop-app quit path and the subagent case. Treat the cleanup below as ongoing hygiene rather than a one-time fix, and re-check after upgrades.

Find them: three columns tell you everything

the one command
ps -Ao pid,ppid,tty,etime,command | grep -i '[c]laude'

The [c]laude bracket trick stops grep from matching its own command line. Then read three columns:

  • ppid — the parent. 1 means the parent is gone and the process was reparented to init. This is the strongest single orphan signal.
  • tty — the controlling terminal. ?? on macOS (or ? on Linux) means no terminal. A live interactive session shows a real tty like s004.
  • etime — elapsed time, as DD-HH:MM:SS. A process with days on it that you do not recognise is not a session you forgot about ten minutes ago.

Add the working directory and you can usually name the session on sight. On macOS:

what directory was that session in?
lsof -a -p <pid> -d cwd -Fn    # prints n/path/to/working/dir

Not everything detached is dead

This is the step the one-liners skip, and it is the one that matters. ppid 1 plus no tty is evidence, not a verdict. Things that legitimately look exactly like that:

  • Anything you started with nohup, disown, tmux, screen, or a background & — deliberately detached, deliberately parentless, possibly halfway through a long job.
  • A session running under a GUI app rather than a terminal. Its parent is the app, not a shell, and it has no tty.
  • MCP servers. A local MCP server is a child process of the client that launched it. When that client is alive, the server is doing its job with no terminal of its own. When the client is gone, the same server becomes a genuine orphan. Same shape, opposite verdict — the difference is entirely whether the parent still exists.

So the actual test is not “is it detached” but “is anything still talking to it”. Parent gone plus no tty plus long idle plus a working directory you are not currently in is a confident yes. Any one of those alone is not.

Why pkill -9 -f claude is worse than it looks

Two independent problems, and the second one surprises people.

It kills your live sessions too

pkill -f matches every process whose full command line contains the pattern, with no regard for parent, terminal or age. The session you are in right now matches. So does every subagent it spawned. With -9 they get SIGKILL, which cannot be caught — no flush of the session transcript, no cleanup of temporary state, no orderly shutdown of the MCP servers it owns. If your goal was to reclaim memory and your result was a lost hour of context, that is a bad trade you made with one command.

It matches processes that are not Claude at all

-f matches the whole command line, and Claude Code's own machinery puts its config path into the command line of ordinary shell processes. Here is a real ps line from the machine this page was written on — an unremarkable zsh running an unrelated command:

a live shell that pkill -f claude would kill
50802  358  /bin/zsh -c source /Users/…/.claude/shell-snapshots/snapshot-zsh-….sh …

That is not a Claude process. It matches -f claude because of a path. Any script, editor, or log tail whose arguments mention a .claude directory matches too. Broad pkill -f patterns are a classic way to take out something you never intended to touch.

The safe sequence

  1. List, and look. ps -Ao pid,ppid,tty,etime,command | grep -i '[c]laude'. Do not pipe anything into a kill yet. Read it.
  2. Account for every live session. Note the pids with a real tty and the ones whose parent is a terminal or IDE you have open. Those are off limits. What remains are candidates.
  3. Confirm each candidate is cold. Parent is 1, no tty, long etime, and a working directory you are not using. Check the directory with the lsof line above if you are unsure — one lookup is cheaper than one wrong kill.
  4. Send SIGTERM first, by pid. kill 12345 12346. Explicit pids, no pattern. SIGTERM is catchable, so a process that has anything to flush gets to flush it.
  5. Wait, then re-list. Most will be gone. Give it a couple of seconds.
  6. Only now, SIGKILL the survivors. kill -9 12345, again by pid. A process that ignored SIGTERM has earned it.
  7. Sweep the MCP servers the same way. Killing a client does not always reap its server children. Re-run the listing with grep -i '[m]cp' and repeat steps 3 to 6 for anything now sitting on ppid 1.

Why the list is longer than the number of sessions you ran

A local MCP server uses the stdio transport: the client launches it as a child process and talks over its stdin and stdout. There is no shared broker and no daemon, so every client connection gets its own copy. Four sessions with the same server configured at user scope is four server processes.

And one registration is usually more than one process, because a launcher does not replace itself with the server. Real ps output from this machine, for a single server name registered twice:

one server name, two registrations, six processes
75398  74931  …/Helpers/disclaimer /opt/homebrew/bin/uv run blender-mcp
75399  75398  /opt/homebrew/bin/uv run blender-mcp
75424  75399  …/blender-mcp/.venv/bin/python …
75400  74931  …/Helpers/disclaimer /opt/homebrew/bin/uv run blender-mcp
75403  75400  /opt/homebrew/bin/uv run blender-mcp
75474  75403  …/blender-mcp/.venv/bin/python …

Two independent chains of three: a wrapper, a launcher, and the server. Multiply by servers, by sessions, by the sessions that leaked instead of exiting, and the arithmetic explains the gigabytes without anything being broken. Kill the head of a chain and the rest usually follows; check, because “usually” is not “always.”

Reducing how often this happens

  • Exit sessions, do not close windows. /exit or Ctrl-D gives the process the clean shutdown path. Closing the window relies on the signal path that is exactly the thing failing here.
  • Sweep on a schedule you actually keep. Start of day is enough. This accumulates slowly and is invisible until it is expensive.
  • Have the agent check at session start. Cheaper than remembering, and it means the agent knows the machine state before it reasons about it.

Making the check something the agent runs

The listing above is a thing you have to remember to do. The more useful version is the agent doing it, because the failure this prevents is not just wasted RAM — it is an agent that tells you a service is running because a note from three weeks ago said so. Model memory is a cache with no invalidation.

whats-running-mcp is the free MIT MCP server we wrote for exactly this. Its agent_sessions tool returns live agent processes split into terminal-attached and detached/orphan, each with pid, uptime and working directory — the classification in step 3, done for you. listening_portscovers the other half: every TCP LISTEN socket with the process that owns it, so a port held by a dead session's dev server stops being a mystery.

install — one line, no config
claude mcp add --scope user whats-running -- npx -y whats-running-mcp

It is detect-only, by construction. Every command it runs is a fixed binary with fixed flags via execFile — no shell, and nothing derived from model input is executed. It will never kill anything, which is deliberate: deciding what is dead is the part that needs a human, and this article is why. Source on GitHub, package on npm, and the rest of the free set on our agent tools page.

Summary

  • Orphans exist because children survive dead parents; a missed SIGHUP is the cause.
  • ppid 1 + no tty + long etime + a directory you are not in = confident orphan. Fewer than all four = check.
  • pkill -9 -f claude kills your live sessions, and matches unrelated processes whose command line merely mentions a .claude path.
  • SIGTERM by explicit pid first, SIGKILL only for survivors.
  • Sweep the MCP server children afterwards — they are not always reaped with the client.

Related reading: how to see which MCP servers are actually running separates configured from connected from running, and running multiple coding agents on one machine covers the three collisions that come with the territory once orphans are handled.

SharePost on X
Free · 13 pages · no upsell inside

Get the Operator Field Kit — free

Six production prompts, the five-step operator setup, and nine rules from our own failure log.

  • 6 complete prompts — printed in full, not previews
  • The five-step setup, each step with a pass/fail test
  • 9 rules from the failure log that produced them

The kit, then the occasional operator note. One click unsubscribes and we never sell the address.

Keep reading