Agent Process Tracking, Audit, And Cleanup

Agents own every process they spawn - dev servers, test/watch runners, automation browsers - and must keep an accurate inventory of what is running. Cleanup is a consequence of the audit, not the whole job.

Coding agents routinely background long-running processes: a pnpm dev server to view a change, a vitest/jest run to check a suite, a Playwright/Chrome instance to test a page. The failure mode is that the agent finishes the task and never stops them. Because each Cursor/Claude/Codex session spawns its own, they pile up. The worst offenders are test runners that hang instead of exiting — they sit at ~100% CPU indefinitely, and a few of them saturate the machine.

The fix is a lifecycle discipline, enforced the same way as the Doctor Pattern: track what you start, audit what is running, then clean up only what the audit proves is yours and no longer needed. It is the Agent Ethos "leave no mess" principle applied to the process table.

The ownership rule

You started it, you stop it. Concretely:

  • Before starting another localhost/dev server, check the process ledger and active listeners so one task does not create six ports.
  • Record every long-running command you start: PID, PGID, port, purpose, command, cwd, and repo root.
  • Keep exactly the one dev server the user is actively using, and any run whose output you are still reading.
  • Kill duplicate or abandoned dev servers, finished or hung test/watch runs, and browser pages/contexts you opened.
  • Never touch the editor/IDE, the agent runtime, the user's interactive shell, or shared MCP servers.

The ledger owner is scripts/process-ledger.sh inside the Browser Testing Skills skill. It writes local state to ~/.cache/agent-processes/ledger.tsv and keeps stop dry-run by default. This turns process work from "infer ownership from ps after the fact" into "know what is running, why it exists, and which process group owns it." Agent Broom packages the same machinery as a portable CLI + SKILL.md repo for other harnesses. Source: SKILL.md, 2026-06-26; Kevin-Liu-01/agent-broom commit 1bab484c38a3061826cbb93cec591c5d786a48db

Why kill by process group

A test command is a tree: pnpm test → node → vitest → fork worker. The CPU is held by the leaf worker, not the pnpm parent. Killing the top PID orphans the worker, which keeps running. Sending the signal to the process group (kill -TERM -<PGID>) takes the whole tree down at once. Use SIGTERM first for a clean shutdown; SIGKILL only for stragglers. Cap nothing — these are local processes, not retries.

Browsers: close, don't nuke

There are two distinct things, and agents conflate them:

  1. The MCP server (playwright-mcp, chrome-devtools-mcp) — launched and reused by the IDE. It powers the browser tools for every active session. Killing it breaks tooling for you and any concurrent agent. It is protected.
  2. The browser the automation drives (headless Chrome under ms-playwright/mcp-chrome). Close the page/context through the browser tool when the task is done; an orphaned Chrome with a dead parent is yours to clean up.

The discipline: close what you open via the tool; only signal-kill a browser process you spawned and orphaned. Do not free memory by killing MCP servers.

Discovery surfaces

  • Cursor terminals folder — per-project metadata for every agent terminal (pid, cwd, command, running_for_ms): head -n 10 ~/.cursor/projects/*/terminals/*.txt. The fastest way to see what is running and for how long.
  • ps — portable CPU/ownership truth: ps -Ao pid,ppid,pgid,%cpu,etime,command. Sort by %cpu to find the hogs; walk ppid to find who spawned it (extension-host or a Cursor shell = agent-spawned).

Machine-wide dev cleanup

Repo-local cleanup is not enough when the leak is outside the current repo: orphaned MCP servers, Flutter/Dart/FVM processes, adb logs, Gradle/Kotlin daemons, iOS simulators, Crashpad dumps, or IDE crash reporter settings. The local devclean.sh helper follows the DevClean shape: safe orphan audit by default, explicit --deep for heavy daemons, --optimize for crash reporters/background agents, and --disk for global dev caches and project artifacts. It is dry-run unless --apply is passed. Source: ImL1s/devclean, 2026-06-26

One difference is intentional: generic crashpad_handler processes are not treated as safe orphan kill targets. On macOS they include normal Codex, Cursor, Chrome, Slack, Discord, and Electron crash reporters with PPID=1. The safe local policy keeps Crashpad cleanup in --optimize where the agent can review the exact files/settings first.

Build & cache artifacts (the disk dimension)

The same "agents leave a mess" pattern shows up on disk, not just in the process table. Builds and caches are regenerable but unbounded: a single .next is routinely hundreds of MB, .turbo and node_modules/.cache grow without limit, and stale build output occasionally causes "works after a clean rebuild" bugs. Clean them when disk is low, when a build misbehaves, or as end-of-session hygiene — they cost nothing to regenerate. Targets: .turbo, .next, .vite, dist/build/out/.output, *.tsbuildinfo, .eslintcache, coverage, test-results, playwright-report, node_modules/.cache, and pnpm store prune for the global store. The rule that keeps this safe: only delete regenerable artifacts, and never a path that still holds git-tracked files — so cleanup can't destroy source. The clean-artifacts.sh helper reports sizes first and only acts on --clean.

The Aiden Bai cache artifact makes the disk dimension concrete: the screenshot shows a 40 GB repo footprint where .turbo/ alone accounts for 36 GB, while actual source directories are about 1.1 GB. The operational lesson is not "delete random large folders"; it is "measure first, then reclaim only regenerable cache." Use pnpm turbo daemon clean or remove .turbo when it is safe, and keep clean-artifacts.sh as the auditable repo-local path because it reports sizes and skips git-tracked files. Source: X/@aidenybai and local image artifact, 2026-07-03

Enforcement

Surface Where Role
Always-on Cursor rule config/cursor/rules/process-hygiene.mdc Injected every session, every repo
Script entrypoint skills/productivity/cleanup-terminals-browsers/scripts/agent-hygiene.sh Track -> audit -> classify -> cleanup -> verify through deterministic commands
Portable package Agent Broom / skills/productivity/cleanup-terminals-browsers/references/agent-broom/ Full upstream CLI + skill package for cross-harness reconstruction
Dev-machine cleanup skills/productivity/cleanup-terminals-browsers/scripts/devclean.sh Devclean-style safe orphans, deep daemons, optimize mode, and disk cleanup
Skill router skills/productivity/cleanup-terminals-browsers/SKILL.md Thin wrapper that tells agents when to run the script and how to extend it
Master agent doc AGENTS.md § Process & Resource Hygiene Cross-agent (Cursor, Claude Code, Codex)
Project docs per-repo AGENTS.md Repo-specific notes (e.g. a suite known to hang)

The helper scripts default to dry-run behavior and are reachable through agent-hygiene.sh. process-ledger.sh list is the normal inventory command. process-ledger.sh stop only reports recorded live process groups unless passed --kill. devclean.sh audits safe orphans by default and only acts with --apply; --deep, --optimize, and --disk are explicit broader modes. audit-processes.sh kills only by category (tests/dev/browser) behind a hardcoded protect-list for IDEs and MCP servers; clean-artifacts.sh deletes only known regenerable artifacts and skips any git-tracked path — so an agent cannot take down its own tooling or destroy source.

Anti-patterns

  • Leaving vitest/jest watchers running "in case" — they hang and pin cores.
  • Starting another localhost:3000 server before checking the ledger and listeners.
  • Forgetting to record the PID/PGID and then guessing ownership during cleanup.
  • Treating process hygiene as cleanup-only. The durable value is the live inventory.
  • kill <PID> on the pnpm parent only, orphaning the worker that holds the CPU.
  • Killing playwright-mcp / chrome-devtools-mcp / the IDE to "free memory."
  • Treating every crashpad_handler with PPID=1 as safe to kill; many are normal active app helpers on macOS.
  • kill -9 first instead of SIGTERM-then-SIGKILL.
  • Re-running a command that never exits, stacking more 100%-CPU workers on top of the hung ones.

Timeline

  • 2026-07-03 | Added the build-cache size artifact: .turbo/ can dominate repo disk usage (36 GB of a 40 GB footprint) while source stays much smaller, so cleanup should be measured, regenerable-only, and script-backed. Source: X/@aidenybai, 2026-03-29
  • 2026-06-30 | Added Agent Broom as the portable CLI + skill packaging layer for this process-hygiene stack. The local cleanup skill remains the canonical owner; the full Agent Broom repo is vendored under its references for source comparison and new-agent bootstrapping. Source: Kevin-Liu-01/agent-broom commit 1bab484c38a3061826cbb93cec591c5d786a48db
  • 2026-06-26 | Reframed agent process hygiene as tracking, audit, and cleanup. The ledger is explicitly a live inventory of running agent-owned processes, with cleanup as a visible downstream action only when the audit proves ownership and staleness. Source: User correction, 2026-06-26
  • 2026-06-26 | Made the implementation script-first: agent-hygiene.sh is the single entrypoint, while the skill remains a thin router. Future domains should be added as script subcommands, following the dry-run/report pattern from kibotu's macOS cleanup gist. Source: User correction and gist, 2026-06-26
  • 2026-06-26 | Added devclean-style machine cleanup: safe orphaned dev processes, explicit deep daemon cleanup, optimize mode, and disk cleanup. The local implementation is dry-run by default and avoids generic Crashpad process killing after dry-run showed active Codex/Cursor/Chrome helpers matched that pattern. Source: User correction and ImL1s/devclean, 2026-06-26
  • 2026-06-26 | Added the process ledger discipline: agents must check/list active localhost servers before starting another one, record long-running commands they start, and clean up recorded process groups dry-run-first. audit-processes.sh now reports localhost listeners. Source: User request, 2026-06-26
  • 2026-06-05 | Incident that motivated this page: in the ariadne repo, three vitest fork workers from earlier agent sessions were each pinned at ~100% CPU for 10h28m, 10h36m, and 10h52m (≈300% total), plus a fourth hung run spawned during cleanup. The pnpm dev server was healthy at 0%. Killing the four test process groups by pgid (SIGTERM) dropped 1-minute load from ~7 to ~4 and left the dev server untouched. The leftover playwright-mcp/chrome-devtools-mcp servers (9–14h old) were live IDE-managed connections at ~0% CPU and were left alone. Codified into the cleanup-terminals-browsers skill + process-hygiene.mdc always-on rule. Source: Kevin, 2026-06-05