Skip to content
ssafayetPublic

About

Find and kill processes left behind by Claude Code sessions, by reading the environment block that outlives their parent.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

Repository files navigation

agent-ps

Find and kill the processes your coding-agent sessions left behind — dev servers, watchers, and background commands that outlived the session that started them.

Supported agents: Claude Code today. The tool is agent-agnostic above a small adapter layer, and ships only adapters that have been verified against a live install — the main verb here is kill, and a wrong session attribution is worse than a missing one. agent-ps --discover helps you find an unsupported agent's markers so an adapter can be written from measurement.

$ agent-ps

ABANDONED  (2)
  PID     PPID  PGID   AGE   SESSION  T   PORTS   COMMAND
  79000   1     79000  4h    999123   2   3000    next-server (v14.2.3)
  87387   1     87387  2d    999123   2d  8080    /bin/zsh -c node server.js

DETACHED  (1)
  PID     PPID  PGID   AGE   SESSION  T   PORTS   COMMAND
  44120   1     44120  12m   50332    2   5173    vite

$ agent-ps --reap
archived 4 log file(s) -> /Users/you/.agent-ps/logs
TERM  -> group -79000 (79000)
TERM  -> group -87387 (87387, 87388)
all groups exited on SIGTERM.

The problem

When an agent runs a background command, the intermediate shell exits and the real process is reparented to launchd (ppid=1). The link back to the session is destroyed, so you cannot find it by walking the process tree — and ppid == 1 on its own is useless as a filter, since a few hundred system daemons match it too.

Supported agents

Agent Status
Claude Code full — live trees and reparented orphans
Codex partial — live process trees only; it stamps no session id, so orphans cannot be attributed
OpenCode, Cursor, Aider, Goose, … not implemented

The mechanism below should apply to any agent that stamps its children with a marker naming the owning session, and everything except the marker itself is already agent-agnostic — but Claude Code is the only adapter anyone has measured. Adding one is a small file: run agent-ps --discover to find your agent's markers, then follow docs/adapters.md.

Adapters ship only once tested against a real install. A guessed session parser misattributes processes, and the main verb here is kill.

The idea

A process's environment block outlives its parent. An agent that stamps its children with a marker naming the owning session can therefore be traced after the parent link is gone. Claude Code stamps CLAUDECODE=1 plus a socket path that embeds the owning session's PID:

CLAUDE_CODE_MESSAGING_SOCKET=/tmp/cc-socks/50332.sock

That survives reparenting. An orphan can still name its own parent long after it stopped having one — which is enough to tell a dead session's leftovers apart from a live session's dev server, and from an app that merely inherited the variable years ago.

Details in docs/architecture.md.

Install

Requires Node 18+ and macOS. (Linux is untested; Windows cannot work — see platform notes.)

git clone https://github.com/ssafayet/agent-ps.git
cd agent-ps
npm link          # or: ln -s "$PWD/bin/agent-ps" /usr/local/bin/agent-ps

Symlink or npm link — do not copy bin/agent-ps on its own, it resolves lib/ relative to itself.

Nothing else is required. The hooks and the menu bar are optional additions.

Quickstart

agent-ps                     # what is out there
agent-ps --reap --dry-run    # what would die
agent-ps --reap              # kill the abandoned ones
agent-ps --agents            # which agents are supported, and their live sessions

--reap targets only abandoned — processes whose owning session is gone. It archives their logs before signalling, and signals process groups, so a whole orphaned tree goes down together even when its individual members are unattributable.

The six states

State Condition Reaped by
managed live descendant of a live session never
detached ppid=1, owning session still alive --reap --all
abandoned ppid=1, owning session dead or socket stale --reap
unowned marked by an agent, but its adapter cannot name the owning session --reap --suspect
suspect unattributable, but ppid=1 with an agent-project cwd --reap --suspect
inherited-app marked, but lives under /Applications etc. never

suspect is the one state that is a guess rather than a proof. It is listed so you can see it, and never reaped without asking.

inherited-app exists because environment is inherited permanently. A GUI app launched once from a session carries the agent's marker for the rest of its life — during development OrbStack showed up as a two-day-old orphan attributed to a long-dead session. Ancestry is not ownership.

Documentation

Architecture attribution tiers, classification, blind spots, platform notes
Agent adapters the adapter contract, finding markers, verifying a new agent
CLI reference every flag of agent-ps and reap-run, exit codes, JSON schema
Logs what can be recovered after the fact, and what must be captured before
Hooks the PreToolUse indexer and the SessionEnd reaper
Menu bar SwiftBar / xbar plugin
Troubleshooting when it finds nothing, or too much
Known issues open defects, with reproductions

Optional extras

Menu bar. A SwiftBar plugin showing the same states in a tray menu, with per-group kill and a dry-run that shows the blast radius first. See docs/menu-bar.md.

Log capture. agent-ps --logs <pid> finds the output of a background command even after its session exited. A PreToolUse hook makes the match exact, and reap-run captures output for processes that would otherwise have none. See docs/logs.md.

Automatic cleanup. A SessionEnd hook that reaps only the exiting session's processes. See docs/hooks.md.

Safety

  • --reap targets only abandoned by default. Live sessions' processes require an explicit --all; heuristic matches require an explicit --suspect.
  • --dry-run prints the blast radius without signalling.
  • Logs are archived before anything is killed.
  • --kill-group refuses any PGID the tool did not itself attribute, so the menu bar cannot become a general-purpose kill(-N).
  • agent-ps never signals its own process group.

Signals are group-wide, so a protected process sharing a PGID with a target would die with it — a group containing one is therefore skipped entirely, with the reason printed. Unknown flags exit non-zero rather than falling through, so a mistyped --dry-run cannot cause a real kill. --json omits process environments and masks credential-shaped values in the command line by default, since an abandoned dev server is exactly what holds your credentials; --unsafe-env returns both in full.

A marker only counts when it is genuinely a variable. ps eww prints the argv ahead of the environment with no delimiter between them, so a command line that merely mentions CLAUDECODE=1 used to read as a marked process — and be reaped as abandoned. The argv is stripped before parsing, and the parse fails closed if it cannot be.

The log store is 0700/0600: it holds captured output and the full text of every background command.

Read known issues before relying on it in anger.

Contributing

See CONTRIBUTING.md.

License

MIT

About

Find and kill processes left behind by Claude Code sessions, by reading the environment block that outlives their parent.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages