The panel showed a single aggregate: the most urgent session, plus a "+N" for the others. That answers "what is the worst thing happening", which is not the question with five projects open -- "what is each of them doing" needs each of them on screen. Each session now gets a chip: state glyph plus a three-character project label, in the centre box just right of the clock. Time in state stays on the first chip only; five counters side by side are a row of numbers, not an answer. Labels are initials for multi-segment names, first letters otherwise. Initials rather than a prefix, because a prefix collapses dev-skills and dev-conventions onto the same "dev" -- exactly the pair that has to stay apart. Collisions, including two sessions in one project where the cwd is identical, take a digit: ds, ds2, ds3. Two rules keep a label still, and the feature is worthless without them. Assignment runs oldest-session-first, so a session starting now takes the suffix instead of displacing one already on screen; and a label belongs to its session until it ends, even after whatever forced the digit has closed. A label that moves under your hand is worse than one carrying a digit that no longer looks necessary. The zellij tab name was considered as the label source and dropped. It arrives asynchronously from dump-layout, so a sticky label would freeze whatever the project name produced first and never adopt it. Digits already separate same-project sessions, so the tab name buys nothing here and stays where it is useful, in the menu. Menu rows now lead with the same label, so the mapping from "ds" to dev-skills is read rather than guessed.
Claude Code Status
Индикатор для GNOME Shell, отвечающий на один вопрос: какая сессия Claude Code ждёт меня прямо сейчас?
Когда открыто несколько сессий в разных проектах, дорого не «не знать, чем каждая занята», а не заметить, что одна из них встала час назад.
Справа от часов стоит по чипу на сессию — значок состояния и трёхбуквенное имя проекта:
◉ ds ● dc ● ds2 ○ pps ◌ umb 9:41
│ │ │ │ └ umbar, тихо
│ │ │ └ pet-project-server, работает
│ │ └ вторая сессия в dev-skills, ждёт
│ └ dev-conventions, ждёт
└ dev-skills, упёрлась в разрешение
Состояния
| Значок | Состояние | Что значит |
|---|---|---|
| диск в кольце | blocked |
упёрлась в запрос разрешения — без вас не сдвинется |
| закрашенный диск | waiting |
ход закончен, ждёт вашего ввода |
| кольцо | busy |
работает |
| тусклое пунктирное кольцо | idle |
запущена, но ничего не просили |
blocked и waiting разведены намеренно. Слитые в одно «требует внимания»,
законченная задача выглядит так же срочно, как заблокированная, — а именно это
различие и решает, переключаться сейчас или после текущей мысли.
Чипы идут по срочности, а внутри одного состояния первой стоит самая давняя: забывается та, что ждёт дольше всех, а не последняя. Время в состоянии показывается только у первого чипа: пять счётчиков рядом — это ряд чисел, а не ответ на вопрос.
Метки чипов
Имя проекта сжимается до трёх знаков: если в имени несколько сегментов
(-, _, camelCase) — инициалы, иначе первые буквы.
| Проект | Чип |
|---|---|
dev-skills |
ds |
dev-conventions |
dc |
pet-project-server |
pps |
claude-code-gnome-extension |
ccg |
jellybit |
jel |
Инициалы, а не просто префикс, важны больше, чем кажется: префикс слил бы
dev-skills и dev-conventions в один dev — ровно тот случай, который надо
развести.
При совпадении — включая две сессии в одном проекте, где cwd один и тот же —
добавляется цифра по старшинству: ds, ds2, ds3. Цифра съедает базу, а не
удлиняет метку, чтобы ряд не расползался.
Два правила держат метки на месте, и без них вся затея рассыпается:
- Назначение идёт от самой давней сессии, так что цифру берёт только что запущенная, а не та, на которую ты сейчас смотришь.
- Выданная метка закреплена за сессией до её конца — даже после того, как закрылась сессия, из-за которой появилась цифра. Метка, переехавшая под рукой, хуже метки с цифрой, которая уже не выглядит нужной.
Сокращение отключается в настройках — тогда в чипах полные имена проектов.
В меню строка начинается с той же метки, чтобы соответствие «ds — это
dev-skills» читалось, а не угадывалось.
Уведомлений на рабочий стол нет — сознательно. notify-send из хука было бы
куда дешевле сделать, но форма неверная: сессия, ждущая двадцать минут, должна
оставаться на виду, а уведомление исчезает в момент, когда его смахнули. Панель
здесь — единственный канал.
Установка
git clone <этот репозиторий> ~/projects/private/claude-code-gnome-extension
cd ~/projects/private/claude-code-gnome-extension
# 1. хуки: научить Claude Code публиковать состояние сессий
./hooks/install.py
# 2. расширение: симлинк, компиляция схемы, включение
ln -s "$PWD" ~/.local/share/gnome-shell/extensions/claude-code-status@git.vakhrushev.me
glib-compile-schemas schemas/
gnome-extensions enable claude-code-status@git.vakhrushev.me
Уже запущенные сессии подхватывают хуки без перезапуска — Claude Code перечитывает
settings.json по мере изменения.
На Wayland правки кода расширения по-прежнему требуют релогина; первое включение — нет.
./hooks/install.py --uninstall снимает регистрацию хуков, не трогая остальное в
~/.claude/settings.json. Копия .bak пишется при каждом запуске.
Как это работает
Хуки Claude Code пишут по одному небольшому JSON-файлу на сессию в
~/.local/state/claude-code-status/<session_id>.json; расширение следит за этим
каталогом через Gio.FileMonitor. Ничего не опрашивается, демона нет — смена
состояния доходит до панели, как только хук завершился.
| Хук | Действие |
|---|---|
SessionStart |
сессия появляется как idle, мёртвые подчищаются |
UserPromptSubmit |
busy |
PostToolUse |
busy |
PreCompact |
busy |
Notification |
blocked или waiting, в зависимости от notification_type |
Stop |
waiting |
SessionEnd |
файл удаляется |
PostToolUse не избыточен: это единственное событие, срабатывающее после выдачи
разрешения, — без него сессия остаётся blocked в панели до конца хода. Пишет он
только при реальной смене состояния, так что обычный случай стоит запуска процесса
и нулевого ввода-вывода.
Stop и SessionEnd зарегистрированы синхронно, в отличие от остальных. Оба
срабатывают, когда процесс вот-вот затихнет, и асинхронный хук, проигравший гонку
с выходом, убивается раньше, чем успевает записать: у claude -p это наблюдалось
как сессия, навсегда застрявшая в busy.
Хуки одной сессии выполняются параллельно, поэтому весь цикл «прочитать — решить —
записать» идёт под flock на <session_id>.json.lock, а событие старше
сохранённого отвергается. Нужно и то, и другое: одна лишь проверка меток времени
всё ещё позволяет хуку, прочитавшему старое состояние до Stop, записать своё
устаревшее решение после него.
Сабагенты не показываются. Батч из восьми задач — это одна строка «работает 40 мин», и это правильная строка: пятый воркер из восьми ни о чём не просит.
zellij
Если сессии живут в табах zellij, меню показывает имя таба, а клик
переключает на него. Поиск идёт через zellij action dump-layout — рабочий
каталог сессии сопоставляется с каталогами пейнов, потому что в дампе раскладки
нет id пейнов и ZELLIJ_PANE_ID для этого не годится. Отсюда следствия: две
сессии в одном табе неразличимы, а сессия, сменившая cwd после открытия пейна,
не найдётся. Строки, которые не разрешились, остаются некликабельными — вместо
того чтобы делать вид, будто клик что-то делает.
Если zellij не используется, выключите в настройках: он стоит одного процесса раз в пару минут.
Известные шероховатости
- Протухшие сессии. Убитый терминал не присылает
SessionEnd. Живость перепроверяется каждые 20 с по/proc/<pid>, файл удаляется — убитая сессия исчезает в пределах этого окна, а не висит вечно. Если хук вообще не смог опознать процесс claude, он записывает pid 0 — «неизвестно», что никогда не путается с «мёртв», — и такие записи истекают по возрасту, через 36 часов. Stopне отличает «закончил» от «сдался». Оба читаются как «ждёт ввода», и решение для вас в обоих случаях одно и то же.- Фокус ведёт к табу zellij, а не к окну. gnome-terminal держит все окна под одним общим серверным процессом, так что окно нельзя сопоставить по pid. Окно поднимается, только если в его заголовке есть имя zellij-сессии; когда совпадения нет, таб всё равно переключается, а фокус остаётся на месте — поднять произвольный терминал хуже, чем не поднимать никакой.
Тесты
gjs -m tests/test-sessions.js # чтение состояния, порядок, живость, слежение
gjs -m tests/test-abbrev.js # метки чипов: сжатие, коллизии, липкость
tests/test-hook.sh # события -> состояния, блокировка, снос файлов
lib/sessions.js намеренно ничего не импортирует из resource:///org/gnome/shell,
а lib/abbrev.js — вообще ничего. Именно это позволяет гонять их вне
композитора — в gjs, а abbrev и в node.
Чтобы посмотреть сырые события хуков, создайте файл-маркер, и каждое событие будет дописываться в него:
touch ~/.local/state/claude-code-status/debug