home tags donate rss
github x

Как следить за лимитами в Codex и Claude Code

Продолжаю настраивать coding-агенты под себя. В прошлый раз был про процесс работы с ними, теперь мелкая деталь — контроль лимитов через statusline.

Я пользуюсь двумя coding-агентами: Codex и Claude Code. Оба меня устраивают, и я спокойно переключаюсь между ними: иногда потому, что в одном заканчиваются лимиты, иногда потому, что просто веду разные задачи в разных клиентах. Основным у меня стал Claude Code — именно там раньше всего появились skills, MCP и прочая рабочая обвязка. За это время вокруг него накопилось много настроек, и далеко не всё я перенёс в Codex. Как это нормально автоматизировать, я тоже не понял. Если кто-то уже решал такую синхронизацию между Claude Code и Codex, напишите в комментариях — буду рад ссылке или совету.

После того как месяц-полтора назад ввели лимиты, стало неудобно постоянно проверять их руками через /status. С контекстом та же история: чтобы понять, сколько ещё осталось до автокомпакта, нужно отдельно дёргать /context. Само по себе это несложно, но делать это постоянно быстро надоедает. При этом хотелось заранее видеть, когда лимит или контекст уже близко, чтобы успеть что-то сохранить руками. После автокомпакта модель у меня часто тупеет и забывает важные факты. Поэтому я полез смотреть, как это вообще можно отслеживать, и выяснил, что у обоих клиентов есть statusline. По умолчанию он и там и там довольно минималистичный и показывает в основном только текущую модель.

В Codex statusline настраивается адекватно. Достаточно написать /statusline и в интерактивном UI выбрать, что показывать. Дальше эти настройки сохраняются в ~/.codex/config.toml.

Я для себя выбрал такой формат:

Codex statusline

gpt-5.4 medium · ~/Code/blog · Context 73% left · 5h 93% · weekly 92% · 258K window · 1.2M used

В Codex нельзя задать полностью свой формат строки. Можно только выбрать набор готовых блоков и их порядок.

В конфиге это выглядит так:

[tui]
status_line = ["model-with-reasoning", "current-dir", "context-remaining", "five-hour-limit", "weekly-limit", "context-window-size", "used-tokens"]
theme = "catppuccin-macchiato"

То есть это можно либо накликать через UI, либо просто отредактировать файл руками.


Что касается claude-code, у него тоже есть команда /statusline. Но вместо интерактивного меню с набором блоков, как в Codex, здесь подошли иначе — по-агентски: робот пытается прочитать мои bash / zsh / fish-конфиги и на их основе сгенерировать statusline.

Очевидно, я не стал давать ему доступ к файлам, потому что не хочу отправлять свои конфиги в Anthropic. Да и не очень понимаю, зачем они им, если на GitHub и так полно dotfiles-репозиториев.

В ~/.claude/settings.json можно указать команду, которая запускается при каждом обновлении statusline. Выглядит это так:

{
  // ...
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "padding": 0,
  },
}

Здесь command — это просто скрипт, который Claude Code будет запускать при обновлении statusline. На GitHub можно найти много разных реализаций. Например, такие безумные решения: sirmalloc/ccstatusline, Owloops/claude-powerline, Haleclipse/CCometixLine, hagan/claudia-statusline, felipeelias/claude-statusline. Безумными я их называю потому, что в эпоху, когда очередной npm-пакет ломают чуть ли не каждый день, ставить дополнительный софт через npm ради настолько простой задачи кажется странной идеей.

Но идею у них позаимствовать можно. Команда запускается при каждом обновлении статуса, в stdin приходит JSON, который можно распарсить и отобразить как угодно. Документация по доступным полям: Claude Code statusline docs

В общем, в statusline приходит примерно такой набор данных:

{
  // shortened example; see docs for the full schema
  "session_id": "<session-id>",
  "transcript_path": "/Users/<user>/.claude/projects/<project>/<session-id>.jsonl",
  "cwd": "/Users/user/Code/blog",
  "session_name": "<session-name>",
  "model": { "id": "claude-sonnet-4-6", "display_name": "Sonnet 4.6" },
  "workspace": { "current_dir": "/Users/user/Code/blog" },
  "version": "2.1.126",
  "output_style": { "name": "default" },
  "context_window": {
    "total_input_tokens": 433,
    "total_output_tokens": 492,
    "context_window_size": 200000,
    "current_usage": {
      "input_tokens": 1,
      "output_tokens": 92,
      "cache_creation_input_tokens": 299,
      "cache_read_input_tokens": 14201,
    },
    "used_percentage": 7,
    "remaining_percentage": 93,
  },
  "exceeds_200k_tokens": false,
  "fast_mode": false,
  "effort": { "level": "high" },
  "thinking": { "enabled": true },
  "rate_limits": {
    "five_hour": { "used_percentage": 62, "resets_at": 1779296400 },
    "seven_day": { "used_percentage": 22, "resets_at": 1779750000 },
  },
}

Вообще для кастомизации терминала я использую starship.rs. У них даже есть интеграция с Claude Code: Statusline for Claude Code. Но блоков для часовых лимитов там нет — после того как их добавили в Claude, starship так и не обновился. Issue на GitHub висит: starship/starship#7441. Насколько я понял, автор репозитория куда-то пропал, и уже несколько месяцев в проекте почти ничего не происходит.

Так что я просто попросил Claude написать bash-скрипт, который выведет строку в том же формате, что в Codex. Вышло что-то такое:

#!/usr/bin/env bash
set -euo pipefail

input="$(cat)"
get() { jq -r "$1" <<<"$input"; }

fmt_tokens() {
  awk -v n="$1" 'BEGIN {
    if      (n >= 1000000) printf "%.1fM", n / 1000000
    else if (n >= 1000)    printf "%.0fK", n / 1000
    else                   printf "%d",    n
  }'
}

model="$(get '.model.display_name')"
dir="$(get '.workspace.current_dir')"
dir="${dir/#"$HOME"/\~}"

context_remaining="$(get '(.context_window.remaining_percentage // 100)')% left"
five_hour="$(get '(100 - .rate_limits.five_hour.used_percentage) | round')%"
weekly="$(get '(100 - .rate_limits.seven_day.used_percentage) | round')%"
window="$(fmt_tokens "$(get '.context_window.context_window_size')")"
used="$(fmt_tokens "$(get '.context_window.context_window_size * .context_window.used_percentage / 100')")"

printf '%s · %s · Context %s · 5h %s · weekly %s · %s window · %s used\n' \
  "$model" "$dir" "$context_remaining" "$five_hour" "$weekly" "$window" "$used"

В итоге оба клиента начинают выглядеть одинаково:

Matching statuslines

Единственный косяк, который я заметил в Claude Code: при открытии терминала statusline не показывается сразу в новых сессиях. Появляется только после первого взаимодействия с чатом (можно вызвать вручную через Cmd+Shift).

На этом всё. На настройку statusline и написание заметки ушло около трёх часов.