Обычно у меня открыто десять-пятнадцать сессий Claude Code. В одной чинятся тесты, в соседнем worktree пилится фронтенд, третья с утра ждёт моего ответа, про четвёртую я забыл. У каждой свои субагенты и MCP-серверы, где-то поднят vite, где-то идёт pytest. В какой-то момент машина на 64 ГБ начала тормозить на каждом переключении окна.

В диспетчере задач была стена одинаковых node и claude. Сколько они едят, видно, а какой сессии принадлежат, нет. Закрывать окна наугад не хотелось.

Я написал claude-load, мод для Claude Code, который раскладывает нагрузку машины по сессиям. Первый запуск показал 15 живых сессий на 10,8 ГБ. Большинство простаивали со смёрженными PR, а одна гоняла забытый Python, родительский процесс которого давно завершился.

Чей это node?

По имени процесса сессии не различить: у всех одни и те же claude, node и bash. Но Claude Code ведёт реестр живых сессий: в ~/.claude/sessions/ на каждую лежит <pid>.json, и там есть почти всё нужное:

{
  "pid": 18636,
  "sessionId": "a3a4722e-…",
  "name": "Refactor the auth middleware",
  "status": "busy",
  "cwd": "C:\\work\\api",
  "entrypoint": "claude-desktop"
}

Формат нигде не описан, поэтому мод читает только эти поля и не падает, если поймал файл в момент перезаписи. Файлы *.key с токенами, которые лежат рядом, он не открывает.

Дальше сэмплер берёт список всех процессов с родителями и от каждого идёт вверх по PPID, пока не встретит PID сессии. Всё дерево под сессией считается её нагрузкой. Пройденные цепочки кэшируются, так что сотни процессов обходятся за один проход.

Выяснилось, что даже простаивающая сессия держит от 0,5 до 0,7 ГБ: от 300 до 450 МБ на сам claude и ещё свой набор MCP-серверов на node. Пятнадцать забытых окон дают несколько гигабайт.

В пейне у каждой сессии видны CPU, память и график за шесть минут. Ниже идут сироты, а в конце всё, что к сессиям не относится, свёрнутое по имени. Если там chrome ×31, Claude можно не подозревать.

Пейн claude-load: CPU и RAM с графиками, сессии, сирота и процессы вне сессий
Пейн claude-load: CPU и RAM с графиками, сессии, сирота и процессы вне сессий

Когда CPU выше 50% или память выше 85%, над полем ввода появляется строка с главными потребителями:

Строка нагрузки над полем ввода
Строка нагрузки над полем ввода

В терминале /load печатает то же самое текстом:

Отчёт /load в терминале
Отчёт /load в терминале

Скриншоты нарисованы на демо-данных. Мод берёт язык из настроек Claude Code.

Сироты и кнопка «завершить»

Сиротами я называю процессы, которые не принадлежат ни одной сессии, но явно остались от какой-то из них: vite из давно закрытой сессии, языковой сервер без редактора, тот самый Python. Их хотелось показать отдельно и дать завершить одной кнопкой.

В первой версии сиротой считался любой dev-процесс вне сессий с заметной нагрузкой. Ревью перед публикацией показало, что так в сироты попадают мой рабочий PowerShell, помощники IDE и conhost, и кнопка предлагает завершить их вместе со всеми потомками.

С самой кнопкой было ещё хуже. Она верила снимку в памяти, который мог устареть на сколько угодно, и не сверяла имя процесса. За это время PID может освободиться и достаться другому процессу, и taskkill /T снёс бы постороннюю программу вместе с её детьми.

Теперь сирота определяется строже. Это дерево, у корня которого родитель действительно мёртв. На Windows это значит, что родительского PID нет среди живых или процесс с этим PID создан позже ребёнка (номер переиспользован). На macOS и Linux корень переподчинён init, systemd или launchd. Показываются только деревья с настоящим инструментом разработки, а не с одними оболочками, и только если они едят от 0,3% CPU или 150 МБ. Терминал, из которого запущена сессия, и сам сэмплер сиротами не считаются никогда.

Чтобы приписать сироту к репозиторию, нужен её рабочий каталог. На Linux это /proc/<pid>/cwd, на macOS его отдаёт lsof. На Windows API для этого нет, поэтому каталог читается из памяти чужого процесса: NtQueryInformationProcess даёт адрес PEB, по смещению 0x20 лежат ProcessParameters, в них по 0x38 лежит CurrentDirectory. В коде это пара вызовов ReadProcessMemory.

Перед завершением мод берёт свежий снимок, отказывается работать со снимком старше 15 секунд и проверяет, что под этим PID всё ещё процесс с тем же именем. Процессы самих сессий и PID, которых нет в снимке, из пейна не завершить. Кнопку можно нажать хоть через полчаса после открытия пейна.

Один сэмплер на машину

Если каждая из пятнадцати сессий будет сама опрашивать процессы, монитор начнёт добавлять нагрузку. Поэтому замеряет один процесс на машину: он пишет ~/.claude/state/load/snapshot.json, а мод в каждой сессии раз в пять секунд читает этот файл, сколько бы сессий ни было.

На Windows сэмплер работает как скрытый процесс PowerShell, его запускает мод, когда снимок устарел. Устройство определили два замера на моей машине: Get-Process отрабатывает за 76 мс, полный запрос CIM с родителями занимает 400 мс. Поэтому раз в четыре секунды читается Get-Process, а дерево родителей из CIM запрашивается, только когда появились новые PID, и не чаще раза в восемь секунд. CPU считается по разнице накопленного времени между проходами.

Сэмплер живёт, пока он кому-то нужен. Каждая сессия раз в 30 секунд трогает файл wanted; если отметок нет три минуты, сэмплер выходит. Если он упал, первая сессия, которая увидит старый снимок, запустит новый, а PID-файл с проверкой командной строки не даст запустить второй. На машине с 12 потоками и 15 сессиями это стоит около 0,35% CPU и 130 МБ памяти.

На macOS и Linux отдельный процесс не нужен, ps и так дешёвый. Там одна из сессий держит «аренду» (файл со своим ID и временем) и сама снимает нагрузку. Если аренду не обновляли 15 секунд, её забирает другая сессия. С CPU пришлось повозиться. В Linux колонка %cpu показывает среднее за всю жизнь процесса, поэтому считать приходится по разнице колонки time. Но time в Linux идёт целыми секундами, и два прохода с интервалом в секунду рисуют всплески, которых не было. Поэтому проходы идут не чаще раза в четыре секунды и не накладываются.

Результат и ограничения

Писали мы его за пару дней вдвоём с моей нейроподружкой Клавдией (Claude Code, Opus 5.5). Первый разбор сработал сразу. Кнопка «Разобрать с Claude» отправляет запрос встроенному скиллу, Claude раскладывает простаивающие сессии и сирот, один раз спрашивает и трогает только то, что одобрено. Из 15 сессий в архив ушли 12: восемь со смёрженными PR и четыре забытые без PR. Занятая память упала с 35,2 до 31,2 ГБ. Ещё я включил в десктопном приложении автоархивацию сессии при мёрже её PR.

На настоящем Mac или Linux мод пока не запускался. Тесты покрывают записанный вывод ps, vm_stat, /proc/meminfo и lsof, но живую машину они не заменяют. CPU на Linux считается грубее, чем на Windows, потому что ps отдаёт время целыми секундами; чтение /proc/<pid>/stat это исправило бы. При строгой политике PowerShell (AllSigned или Constrained Language Mode) сэмплер не стартует, и мод об этом пишет вместо бесконечного «Жду…».

Реестр сессий не документирован, и если Anthropic поменяет формат ~/.claude/sessions, мод придётся обновлять. Рабочий каталог на Windows читается из памяти чужого процесса. Это только чтение и только процессов текущего пользователя, но антивирусу или EDR это может не понравиться. Сетевых запросов и телеметрии у мода нет.

Установка

Нужен Claude Code 2.1.287 или новее. Команда ниже добавляет репозиторий как маркетплейс и ставит мод для текущего пользователя. macOS, Linux и Git Bash:

claude plugin marketplace add Gerrux/claude-load && claude plugin install claude-load@claude-load --config language=auto

PowerShell:

claude plugin marketplace add Gerrux/claude-load; if ($?) { claude plugin install claude-load@claude-load --config language=auto }

В десктопном приложении без терминала: «+» рядом с полем ввода, Plugins, Add marketplace, Gerrux/claude-load, Add plugin. После установки выполните /reload-plugins и /load. Код на GitHub под MIT, есть README на русском, про сами моды есть документация Claude Code.

Сколько сессий обычно держите открытыми вы? Если вы на Mac или Linux, напишите, что показал /load и что сломалось.

Комментарии (0)