Claude Code の statusLine を3行にしたら、日々のトークンモニタリングが快適になった

CARTA fluct エンジニア の なっかー @konsent_nakka です。

ちょっとした不安、ありませんか

Claude Code を流し見していると、「いまモデル何だっけ」「コンテキスト残量どれくらい?」「rate limit はあとどれくらい?」が、やんわり気になることがあります。

/status/context を都度叩けば確認できるんですが、思考と作業の流れが切れる。結局そのままスルーして、画面に Compacting... が出てから「あ、まだやることあったのに」と後悔する、というのを何度かやりました。

statusLine を3行構成に育てたところ、この「うっすら不安」がほぼ消えました。同じことで困っている人向けに、設定をそのままコピペできる形でまとめておきます。

そもそも statusLine とは

Claude Code のプロンプト入力欄のすぐ下に常時表示される情報バーが statusLine です。settings.json から外部スクリプトを呼び出す形で中身を自由にカスタマイズでき、ターンごとに再描画されます。デフォルトでは最低限の情報しか出ていませんが、ここに「いま気になっている情報」を盛り込めるのが醍醐味で、本記事ではこの statusLine を3行に拡張する話をしていきます。

私の statusLine(実物)

実際にいま使っているのがこちらです。

📂 ~/dev/my/techblog │ 🌿 master +12 -3
🧠 ███████░░░░░░░░ 47% │ 💪 Opus 4.7 (xhigh)
🕐 ██████░░░░░░░░░ 40% │ 📅 ███░░░░░░░░░░░░ 18%

3行に分けて、こんな情報を載せています。

  • 1行目: いま触っている場所(cwd / ブランチ名 / 差分行数)
  • 2行目: いま使っている思考リソース(context 残量バー / モデル名 + effort)
  • 3行目: 利用枠の消費状況(5h rate limit / 7d rate limit)

プロンプト入力欄のすぐ上に常時表示されるので、流し見しているときに視線が自然と statusLine に落ちて、「context 70% 超えたから新しいセッション切るか」みたいな判断ができるようになりました。

特に効いているのは2行目です。Opus を使っているつもりがいつの間にか別モデルに切り替わっていた、という事故に早めに気づけるようになります。3行目の rate limit も、5h 枠が溜まってきたら少しペース落とそう、といった日々の運用判断につながっています。

設定方法(コピペで動く)

statusLine は settings.json から外部スクリプトを呼び出す形にできます。

~/.claude/settings.json

{
  "statusLine": {
    "type": "command",
    "command": "bash ~/.claude/statusline-command.sh"
  }
}

呼ばれるスクリプト本体はこちらです。

~/.claude/statusline-command.sh

#!/bin/bash

input=$(cat)

cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""')
model=$(echo "$input" | jq -r '.model.display_name // ""')
effort=$(echo "$input" | jq -r '.effort.level // empty')
used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
five_hour_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
seven_day_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')

render_bar() {
  local pct="$1"
  local total=15
  local rounded
  printf -v rounded "%.0f" "$pct"
  local filled=$(( rounded * total / 100 ))
  [ "$filled" -gt "$total" ] && filled=$total
  local empty=$(( total - filled ))
  local bar=""
  for (( i=0; i<filled; i++ )); do bar="${bar}█"; done
  for (( i=0; i<empty; i++ )); do bar="${bar}░"; done
  printf '%s' "$bar"
}

# カレントディレクトリ(ホームディレクトリを ~ に短縮)
short_cwd="${cwd/#$HOME/~}"

# Gitブランチ名(ロックファイルを避けるため GIT_OPTIONAL_LOCKS=0 を使用)
branch=""
if git_branch=$(GIT_OPTIONAL_LOCKS=0 git -C "$cwd" symbolic-ref --short HEAD 2>/dev/null); then
  branch="$git_branch"
elif git_branch=$(GIT_OPTIONAL_LOCKS=0 git -C "$cwd" rev-parse --short HEAD 2>/dev/null); then
  branch="${git_branch}(detached)"
fi

# git 行数差分(+追加行数 -削除行数)
git_changes=""
if [ -n "$branch" ]; then
  diff_stat=$(GIT_OPTIONAL_LOCKS=0 git -C "$cwd" diff --numstat 2>/dev/null)
  diff_cached=$(GIT_OPTIONAL_LOCKS=0 git -C "$cwd" diff --cached --numstat 2>/dev/null)
  combined=$(printf '%s\n%s' "$diff_stat" "$diff_cached")

  ins=0
  del=0
  while IFS=$'\t' read -r a d _; do
    [[ "$a" =~ ^[0-9]+$ ]] && ins=$(( ins + a ))
    [[ "$d" =~ ^[0-9]+$ ]] && del=$(( del + d ))
  done <<< "$combined"

  parts_git=()
  [ "$ins" -gt 0 ] && parts_git+=("+${ins}")
  [ "$del" -gt 0 ] && parts_git+=("-${del}")
  if [ ${#parts_git[@]} -gt 0 ]; then
    git_changes=" $(IFS=' '; echo "${parts_git[*]}")"
  fi
fi

# コンテキストプログレスバー(15マス)
ctx_bar=""
ctx_pct_label=""
if [ -n "$used_pct" ]; then
  printf -v used_rounded "%.0f" "$used_pct"
  ctx_bar=$(render_bar "$used_pct")
  ctx_pct_label="${used_rounded}%"
fi

# Line 1: 📂 <cwd> │ 🌿 <branch><changes>
line1="📂 ${short_cwd}"
if [ -n "$branch" ]; then
  line1="${line1} │ 🌿 ${branch}${git_changes}"
fi

# Line 2: 🧠 <bar> <pct> │ 💪 <model>
line2=""
if [ -n "$ctx_bar" ]; then
  line2="🧠 ${ctx_bar} ${ctx_pct_label}"
else
  line2="🧠 ░░░░░░░░░░░░░░░ -"
fi
if [ -n "$model" ]; then
  model_label="$model"
  [ -n "$effort" ] && model_label="${model_label} (${effort})"
  line2="${line2} │ 💪 ${model_label}"
fi

# Line 3: 🕐 <5h_bar> <pct> │ 📅 <7d_bar> <pct>
if [ -n "$five_hour_pct" ]; then
  printf -v five_hour_rounded "%.0f" "$five_hour_pct"
  line3="🕐 $(render_bar "$five_hour_pct") ${five_hour_rounded}%"
else
  line3="🕐 ░░░░░░░░░░░░░░░ -"
fi
if [ -n "$seven_day_pct" ]; then
  printf -v seven_day_rounded "%.0f" "$seven_day_pct"
  line3="${line3} │ 📅 $(render_bar "$seven_day_pct") ${seven_day_rounded}%"
else
  line3="${line3} │ 📅 ░░░░░░░░░░░░░░░ -"
fi

printf '%s\n%s\n%s' "$line1" "$line2" "$line3"

要点だけ短く補足します。

Claude Code は statusLine スクリプトの stdin に JSON を流してくれるので、必要な値を jq で取り出して使っています。workspace.current_dirmodel.display_namecontext_window.used_percentagerate_limits.five_hour.used_percentage あたりが今回使っているフィールドです。

パーセント値からのプログレスバーは render_bar 関数で15マス分の を組み立てています。マスの総数を変えれば長さも調整できます。

git のブランチ名・差分行数を取る箇所では GIT_OPTIONAL_LOCKS=0 を必ずつけています。statusLine はターンごとに呼ばれるため、git index.lock と衝突して値が空になる事故が起きやすく、これを知らないと「たまにブランチ名が消える」という地味な不具合に詰まります。

便利tips

ここまでをベースに、好みで足し引きできます。

stdin の JSON には output_stylecost といったフィールドも入ってきます。気になっている人は2行目あたりに足すと良さそうです。バーのマス数も local total=15 を変えれば伸縮できるので、狭い画面なら10マスでも十分読めます。

statusLine は毎ターン実行されるので、重い処理を入れるとレスポンスがもっさりします。外部コマンドを呼ぶ箇所は最小限にしておくのがおすすめです。

何を載せるかを選ぶ過程は地味に面白くて、残量が気になるなら context、コストが気になるなら cost、ブランチ管理が雑になりがちなら branch、というふうに自分の不安が statusLine に染み出してきます。逆に1週間使って一度も視線を落とさなかった項目は、たぶん要りません。

まとめ

statusLine は地味な機能ですが、毎日視界に入る道具なのでちゃんと育てると効きます。設定自体は1時間もかからず、上のスクリプトをそのままコピペでも動きます。

「デフォルトで十分」と思っていた人ほど、3行に増やすと普段の「うっすら不安」が言語化される感覚があると思います。よければ ~/.claude/settings.json を開くところから試してみてください。