# Claude Code Status Индикатор для GNOME Shell, отвечающий на один вопрос: **какая сессия Claude Code ждёт меня прямо сейчас?** Когда открыто несколько сессий в разных проектах, дорого не «не знать, чем каждая занята», а не заметить, что одна из них встала час назад. Панель называет сессию, а не только состояние. ## Состояния | В панели | Состояние | Что значит | |---|---|---| | диск в кольце | `blocked` | упёрлась в запрос разрешения — без вас не сдвинется | | закрашенный диск | `waiting` | ход закончен, ждёт вашего ввода | | кольцо | `busy` | работает | | тусклое пунктирное кольцо | `idle` | запущена, но ничего не просили, либо ничего не запущено | `blocked` и `waiting` разведены намеренно. Слитые в одно «требует внимания», законченная задача выглядит так же срочно, как заблокированная, — а именно это различие и решает, переключаться сейчас или после текущей мысли. Панель показывает **самую приоритетную** сессию и счётчик `+N`, если в том же состоянии есть другие. Внутри состояния выигрывает **самая давняя**: забывается та, что ждёт дольше всех, а не последняя. **Уведомлений на рабочий стол нет** — сознательно. `notify-send` из хука было бы куда дешевле сделать, но форма неверная: сессия, ждущая двадцать минут, должна оставаться на виду, а уведомление исчезает в момент, когда его смахнули. Панель здесь — единственный канал. ## Установка ```sh 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/.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` на `.json.lock`, а событие старше сохранённого отвергается. Нужно и то, и другое: одна лишь проверка меток времени всё ещё позволяет хуку, прочитавшему старое состояние до `Stop`, записать своё устаревшее решение после него. Сабагенты не показываются. Батч из восьми задач — это одна строка «работает 40 мин», и это правильная строка: пятый воркер из восьми ни о чём не просит. ## zellij Если сессии живут в табах zellij, меню показывает **имя таба**, а клик переключает на него. Поиск идёт через `zellij action dump-layout` — рабочий каталог сессии сопоставляется с каталогами пейнов, потому что в дампе раскладки нет id пейнов и `ZELLIJ_PANE_ID` для этого не годится. Отсюда следствия: две сессии в одном табе неразличимы, а сессия, сменившая `cwd` после открытия пейна, не найдётся. Строки, которые не разрешились, остаются некликабельными — вместо того чтобы делать вид, будто клик что-то делает. Если zellij не используется, выключите в настройках: он стоит одного процесса раз в пару минут. ## Известные шероховатости - **Протухшие сессии.** Убитый терминал не присылает `SessionEnd`. Живость перепроверяется каждые 20 с по `/proc/`, файл удаляется — убитая сессия исчезает в пределах этого окна, а не висит вечно. Если хук вообще не смог опознать процесс claude, он записывает pid 0 — «неизвестно», что никогда не путается с «мёртв», — и такие записи истекают по возрасту, через 36 часов. - **`Stop` не отличает «закончил» от «сдался».** Оба читаются как «ждёт ввода», и решение для вас в обоих случаях одно и то же. - **Фокус ведёт к табу zellij, а не к окну.** gnome-terminal держит все окна под одним общим серверным процессом, так что окно нельзя сопоставить по pid. Окно поднимается, только если в его заголовке есть имя zellij-сессии; когда совпадения нет, таб всё равно переключается, а фокус остаётся на месте — поднять произвольный терминал хуже, чем не поднимать никакой. ## Тесты ```sh gjs -m tests/test-sessions.js # чтение состояния, порядок, живость, слежение tests/test-hook.sh # события -> состояния, блокировка, снос файлов ``` `lib/sessions.js` намеренно ничего не импортирует из `resource:///org/gnome/shell` — именно это позволяет гонять его в обычном `gjs`, вне композитора. Чтобы посмотреть сырые события хуков, создайте файл-маркер, и каждое событие будет дописываться в него: ```sh touch ~/.local/state/claude-code-status/debug ```