I usually have ten to fifteen Claude Code sessions open. One is fixing tests, another is working on a frontend in a neighbouring worktree, a third has been waiting for my answer since the morning, and I forgot about the fourth. Each has its own subagents and MCP servers, with a vite server up here and a pytest run there. At some point my 64 GB machine started lagging on every window switch.
Task Manager showed a wall of identical node and claude processes. I could see how much they were eating, but not which session they belonged to, and I didn't want to close windows at random.
So I wrote claude-load, a Claude Code mod that splits the machine's load by session. The first run showed 15 live sessions using 10.8 GB. Most had been idle for a while with merged PRs, and one was running a forgotten Python process whose parent had exited long ago.
Code (MIT): github.com/Gerrux/claude-load
Whose node is this?
You can't tell sessions apart by process name, since they all run the same claude, node and bash. But Claude Code keeps a registry of live sessions: ~/.claude/sessions/ has a <pid>.json for each one, with almost everything you need:
{
"pid": 18636,
"sessionId": "a3a4722e-…",
"name": "Refactor the auth middleware",
"status": "busy",
"cwd": "C:\\work\\api",
"entrypoint": "claude-desktop"
}
The format isn't documented, so the mod reads only these fields and doesn't fail if it catches a file mid-rewrite. It never opens the *.key files with tokens that sit next to them.
The sampler takes the list of all processes with their parents and walks up the PPID chain from each one until it hits a session's PID. The whole tree under a session counts as its load. Walked chains are cached, so hundreds of processes take a single pass.
It turned out that even an idle session holds 0.5 to 0.7 GB: 300 to 450 MB for claude itself, plus its own set of MCP servers on node. Fifteen forgotten windows add up to several gigabytes.
In the pane, each session shows CPU, memory and a six-minute graph. Orphans come next, and at the bottom everything that isn't a session is folded by name. If it says chrome ×31 there, Claude is off the hook.
When CPU goes over 50% or memory over 85%, a line with the top consumers appears above the prompt:
In the terminal, /load prints the same thing as text:
The screenshots are rendered from demo data, because my real sessions are named after work projects.
Orphans and the "end" button
By orphans I mean processes that don't belong to any session but clearly came from one: vite from a session I closed long ago, a language server with no editor, that Python run. I wanted to show them separately and let you end them with one button.
In the first version, an orphan was any dev process outside sessions with noticeable load. The review before release showed that this pulled in my working PowerShell, IDE helpers and conhost, and the button offered to end them along with all their children.
The button itself was worse. It trusted an in-memory snapshot that could be arbitrarily old, and it didn't check the process name. In the meantime a PID can be freed and handed to another process, and taskkill /T would have taken down an unrelated program together with its children.
The rule is stricter now. An orphan is a tree whose root's parent is really dead. On Windows that means the parent PID isn't among the living, or the process with that PID was created after the child (the number was reused). On macOS and Linux the root has been reparented to init, systemd or launchd. Only trees with a real dev tool in them are shown, not just shells, and only if they use at least 0.3% CPU or 150 MB. The terminal a session was started from and the sampler itself are never orphans.
To attribute an orphan to a repository you need its working directory. On Linux it's /proc/<pid>/cwd, and on macOS lsof reports it. Windows has no API for this, so the mod reads it from the other process's memory: NtQueryInformationProcess gives the PEB address, ProcessParameters is at offset 0x20, and CurrentDirectory is at 0x38 inside those. In code that's a couple of ReadProcessMemory calls.
Before ending anything, the mod takes a fresh snapshot, refuses to act on one older than 15 seconds, and checks that the PID still belongs to a process with the same name. Session processes and PIDs missing from the snapshot can't be ended from the pane. You can click the button half an hour after opening the pane and it's still safe.
One sampler per machine
If each of fifteen sessions polled processes on its own, the monitor would add load of its own. So exactly one process per machine does the sampling: it writes ~/.claude/state/load/snapshot.json, and the mod in each session reads that file every five seconds, however many sessions there are.
On Windows the sampler is a hidden PowerShell process that the mod starts when the snapshot is stale. Two measurements on my machine shaped it: Get-Process takes 76 ms, while a full CIM query with parents takes 400 ms. So every four seconds it reads Get-Process, and the parent tree from CIM is queried only when new PIDs appear, at most once every eight seconds. CPU share comes from the difference in accumulated time between passes.
The sampler lives as long as someone needs it. Each session touches a wanted file every 30 seconds; after three minutes without a touch, the sampler exits. If it crashes, the first session to see a stale snapshot starts a new one, and a PID file with a command-line check prevents a second copy. On a 12-thread machine with 15 sessions this costs about 0.35% CPU and 130 MB of memory.
On macOS and Linux no separate process is needed, since ps is cheap. There, one session holds a "lease" (a file with its ID and a timestamp) and does the sampling itself. If the lease isn't renewed for 15 seconds, another session takes over. CPU took some fiddling. On Linux the %cpu column is the average over the process's whole life, so the mod uses the difference in the time column instead. But Linux reports time in whole seconds, and two passes one second apart draw spikes that never happened. So passes run at most once every four seconds and never overlap.
Results and limitations
I wrote it over a couple of days together with my AI sidekick, Claude Code (Opus 5.5). The first cleanup worked right away. The "Sort it out with Claude" button sends a request to a bundled skill; Claude sorts idle sessions and orphans, asks once, and touches only what you approve. Of 15 sessions, 12 went to the archive: eight with merged PRs and four forgotten ones without a PR. Used memory dropped from 35.2 to 31.2 GB. I also turned on auto-archiving a session when its PR is merged in the desktop app.
The mod hasn't run on a real Mac or Linux box yet. Tests cover recorded ps, vm_stat, /proc/meminfo and lsof output, but that doesn't replace a live machine. CPU on Linux is coarser than on Windows because ps reports time in whole seconds; reading /proc/<pid>/stat would fix that. Under a strict PowerShell policy (AllSigned or Constrained Language Mode) the sampler won't start, and the mod says so instead of showing "Waiting…" forever.
The session registry is undocumented, so if Anthropic changes the ~/.claude/sessions format, the mod will need an update. On Windows the working directory is read from another process's memory. It's read-only and limited to the current user's processes, but an antivirus or EDR may not like it. The mod makes no network requests and has no telemetry.
Install
You need Claude Code 2.1.287 or newer. The command below adds the repo as a marketplace and installs the mod for your user. On macOS, Linux and Git Bash:
claude plugin marketplace add Gerrux/claude-load && claude plugin install claude-load@claude-load --config language=auto
In PowerShell:
claude plugin marketplace add Gerrux/claude-load; if ($?) { claude plugin install claude-load@claude-load --config language=auto }
In the desktop app without a terminal: "+" next to the prompt, Plugins, Add marketplace, Gerrux/claude-load, Add plugin. Then run /reload-plugins and /load. The Claude Code docs cover mods in general.
How many sessions do you usually keep open? If you're on a Mac or Linux, tell me what /load showed and what broke.
This post was written with the help of Claude, the same way the mod was.



Top comments (0)