跳到正文
DEV Community· Ilya Kalinin·· 2 小時前AI 評分63

claude-load mod 顯示 Claude Code 15 個工作階段合佔 10.8 GB 記憶體

15 Claude Code sessions were sitting on 11 GB of RAM. I built a mod to see which one

AI 導讀

作者推出開源 Claude Code mod claude-load(github.com/Gerrux/claude-load),可按工作階段查看 CPU、記憶體及孤立程序的資源佔用。

正文

I usually have ten to fifteen Claude Code sessions open. One is fixing tests in one repo, another is working on a frontend in a neighbouring worktree, a third has been waiting for my answer since the morning, and the fourth I honestly forgot about. Each has its own subagents and MCP servers, there's a vite server up here and a pytest run there. It worked for a while, until my 64 GB machine started thinking twice about every window switch.

I opened Task Manager and saw what everyone who works like this has probably seen: a wall of identical node and claude processes. How much they eat, you can see. Whose they are, you can't. I could have closed a couple of windows at random and hoped I got the right one, but I didn't want to guess.

That's how claude-load started: a Claude Code mod that splits the machine's load by session. The very first run showed 15 live sessions holding 10.8 GB together. Most had been idle for a while with merged PRs, and one was running a forgotten Python process whose parent was already gone.

Code (MIT): github.com/Gerrux/claude-load

Whose node is this?

The process name is useless here: every session has the same claude, node and bash. But Claude Code keeps a registry of live sessions. In ~/.claude/sessions/ there's a <pid>.json file 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 anywhere, so the mod reads only these fields and copes with the moment Claude rewrites the file. Next to them are *.key files with tokens, which the mod never opens.

The rest is a tree walk. The sampler takes the list of all processes with their parents and climbs the PPID chain from each one until it hits a session's PID. Everything under a session is its load. Walked chains are cached, so even hundreds of processes take a single pass.

The first surprise came before any charts. A session that does nothing still holds 0.5–0.7 GB: claude itself is roughly 300–450 MB, plus a set of MCP servers on node, one set per session. Fifteen forgotten windows are several gigabytes just sitting there.

Here's what it looks like in the pane. Each session gets CPU, memory and a six-minute graph, orphans get their own block (more on them below), and at the bottom everything that isn't a session is folded by name. A line like chrome ×31 sometimes clears Claude of all suspicion right away.

claude-load pane: CPU and RAM with graphs, sessions, an orphan and processes outside sessions

The screenshots are rendered from demo data, since my real sessions are named after work projects.

The button that could have killed someone else's process

The interesting part is processes that don't belong to any session but clearly came from one. A vite server started by a session I closed long ago. A language server without an editor. That forgotten Python run. I wanted to show them separately and let you end them with one button.

The first version was simple: an orphan is any dev process outside sessions with noticeable load. It lasted until the review (more on that later). It turned out my live PowerShell, IDE helpers and conhost were showing up as orphans, and the button next to them was happily offering to end them along with their whole tree of children.

The same review found something worse. The "end" button trusted the in-memory snapshot, which could be arbitrarily old, and it didn't check the process name. Between the snapshot and the click, a PID can be freed and handed to someone else. taskkill /T would then have hit a completely unrelated process along with its children.

Now it's stricter. 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, so the number has been reused. On macOS and Linux the root has been reparented to init, systemd or launchd. A tree is shown only if it contains a real dev tool, not just shells, and uses 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, on macOS lsof gives it to you. Windows has no ready API, so the directory has to be read from the other process's memory: NtQueryInformationProcess gives the PEB address, ProcessParameters sits at offset 0x20, and CurrentDirectory at 0x38 inside those. It sounds scarier than it is: a couple of ReadProcessMemory calls.

And right before ending anything, the mod takes a fresh snapshot, refuses if it's older than 15 seconds, and checks that the PID still belongs to a process with the same name. Session processes and PIDs that aren't in the snapshot can't be ended from the pane at all. So if you click "Yes, end it" half an hour after opening the pane, nothing bad happens.

The monitor must not become part of the problem

If each of fifteen sessions polls processes on its own, the load monitor becomes load. So exactly one process per machine does the sampling. It writes ~/.claude/state/load/snapshot.json, and the mod in each session only reads that file every five seconds. Add as many sessions as you like, the cost doesn't grow.

On Windows the sampler is a hidden PowerShell process that the mod starts when it sees the snapshot is stale. Two measurements on my machine decided the whole design: Get-Process takes 76 ms, while a full CIM query with parents takes 400 ms. So every four seconds it reads the cheap Get-Process, and the parent tree from CIM is queried only when new PIDs appear, and no more than once every eight seconds. CPU share is computed from the difference in accumulated time between passes.

The sampler lives as long as someone wants it. Each session touches a wanted file every 30 seconds, and after three minutes without a touch the sampler exits on its own. If it crashes, the first session to notice a stale snapshot starts a new one, and a PID file with a command-line check prevents a second one. On a 12-thread machine with 15 sessions all of this costs about 0.35% CPU and 130 MB of memory.

On macOS and Linux no separate process is needed, ps is cheap enough. There, one session's mod 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 it over. CPU took some fiddling. The %cpu column on Linux is the average over the process's whole life, so the mod has to use 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 no more than once every four seconds and never overlap.

Mods, the engine and five rejections

Mods arrived in Claude Code 2.1.287. They're a new kind of plugin: a single TypeScript module that hooks into the agent's own process. A hook looks like ($, e, next), where $ is the engine interface with files, processes, timers, storage and UI, e is the event, and next passes it on. The module has no Node and no DOM, everything external goes through $. The UI is built from JSX: the terminal has Box, Text and Button, and the desktop adds Svg.

claude-load hangs on five points. session.start sets up a timer and registers /load, command.run answers the command, ui.render draws the pane and the line above the prompt, and $.prompt.submit asks the session to sort out the load. The line above the prompt appears by itself when CPU goes over 50% or memory over 85%, and shows the top consumers:

Load line above the prompt

On the desktop the graphs are real SVG. In the terminal the same data is drawn with ▁▂▃▅▇, and /load prints a short report:

/load report in the terminal

Before loading, the engine statically checks the module, and I learned some of its rules only from rejections. First it refused a function I passed $ to inside a closure: $ may only go to top-level functions in the same file. I had to move the state into a separate object and lift the functions up. Then it turned out $ can't be passed through an import either, so the other files hold only pure functions. $.env.get accepts the variable name only as a literal, so $.env.get(win ? 'USERPROFILE' : 'HOME') became a ternary of two calls. Link opens only https, so jumping to another session via claude:// became a button that asks the OS to open the link. And Box simply has no borderTop, so the line under the header gave way to an all-caps header.

The good news: mods have a real test kit. claude plugin test runs the mod on the live engine with fake files, processes, clocks and even pane button clicks. The $.env.get bug was caught by a test, claude plugin validate missed it. There's a catch: the kit resolves paths on the host OS, so in a test of the Linux branch on Windows, /home/u arrived in the mock as C:\home\u, and the mock had to normalize it back. In the end there are 59 tests: parsers for ps, vm_stat, /proc/meminfo and lsof, the lease, filters, the kill confirmation and all eight languages on both surfaces.

I didn't write the code

The title says "I built", and that's a stretch: I barely wrote any of it by hand. The mod was made entirely in Claude Code (Opus 5.5) in a couple of days, and I was the client and a picky reviewer. My messages in the session history are mostly like "make it prettier", "add filters", "this looks really ugly". The last one was about the line above the prompt, which is how it got its SVG graph and coloured CPU and RAM.

Orchestration helped the most. The main session held the architecture and handed tasks to subagents, each with its own files so they wouldn't fight over the same code. One wrote the macOS and Linux sampler with tests on recorded command output, since I had no other way to check it on my Windows machine. Others translated the UI and the README. The dictionaries are typed, so a missing key fails a test, and plural forms come from Intl.PluralRules, which matters for languages like Russian with three plural forms.

Before publishing there was one Opus review pass over the whole repository. The orphans and the reused PID above are its findings. In total it fixed nine local defects itself, each with a test. For example, a profile path with an apostrophe like C:\Users\O'Brien broke the sampler launch command, and passes on Linux could overlap and draw fake spikes. Design decisions it didn't touch, it handed back to me.

Even the README screenshots were made by Claude. It built an HTML mockup with the same SVG icons and graphs as the mod and captured it in headless Chrome. Real screenshots with my work project names were not supposed to end up in a public repo.

Twelve windows to the archive

The first cleanup worked right away. The "Sort it out with Claude" button sends a request to a bundled skill, and Claude sorts idle sessions and orphans, asks once and touches only what you approve. Of 15 live 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, so forgotten windows stop piling up.

Now, what the mod can't do. It hasn't run on a real Mac or Linux box yet: everything is covered by tests on recorded ps, vm_stat, /proc/meminfo and lsof output, but that's not the same thing. CPU on Linux is coarser than on Windows because ps reports time in whole seconds; reading /proc/<pid>/stat would fix that. A strict PowerShell policy (AllSigned or Constrained Language Mode) will stop the sampler from starting, and the mod will say so instead of an endless "Waiting…".

Two more risks are out of my hands. The session registry is undocumented, and if Anthropic changes the ~/.claude/sessions format, the mod will need an update. And on Windows the working directory is read from another process's memory. It's read-only and only your own processes, but an antivirus or EDR may not like it. Everything else stays on your machine: no network requests, no telemetry.

Try it

You need Claude Code 2.1.287 or newer. One command 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 you can skip the terminal: "+" next to the prompt → Plugins → Add marketplace → Gerrux/claude-load → Add plugin. Then run /reload-plugins and /load. The code is on GitHub under MIT, and the Claude Code docs cover mods in general.

How many sessions do you keep open? If you're on a Mac or Linux, tell me what /load showed and what broke: right now that's the most useful feedback the mod can get.

This post was written with the help of Claude, the same way the mod was.

來源:DEV Community · dev.to