Statusline システム — 3+1 行レイアウト完全ガイド
測定できなければ制御できません。エージェンティック開発はひとつのセッションで数十万トークンを使い、コンテキストウィンドウ(context window、モデルが一度に記憶できる対話の総量)を急速に埋め、複数のエージェント(自ら作業する AI の助手)が並列で回り、プロンプトキャッシュ(prompt cache、同じ文脈を再利用して費用を減らす手法)の命中を左右します。このすべてがターミナルの中で目に見えなければ、「なぜ今度のセッションは費用が2倍になったのか」という問いに答えられません。カスタム statusline システムはまさにこの地点から出発します。トークノミクス(tokenomics、トークンを経済的に使う考え方)は測定から始まるので、コンテキスト使用率とキャッシュ命中率、rate limit の消費率をターミナル下部に常に表示しておきます。
この文書は、statusline が何を示すか、データがどう流れるか、そしてコンテキストが埋まるときどんな信号が出るかを入門書の水準で整理します。セグメント書式の細部よりも「なぜこの情報が要るのか、どう読むのか」を先に説明します。
エージェンティックコーディングで費用と品質を決める変数は5つです。どのモデルを使うか、どの推論深度で回っているか、コンテキストウィンドウがどれだけ埋まったか、rate limit がどれだけ残っているか、そしてプロンプトキャッシュがちゃんと効いているか。この5つは互いにつながっています。コンテキストが埋まれば SSE ストール(stream stall、ストリーミングが止まる現象)が起き、キャッシュが効かなければ費用がすぐに上がり、rate limit が底をつけば重い作業を止めざるを得ません。
問題は、これらの変数がデフォルトでは見えないことです。Claude Code 自身のステータスバーは豊富ですが、MoAI ワークフローが扱う情報 — アクティブな SPEC(要件仕様書)、現在の PR のレビュー状態、ハンドオフ(handoff、セッションをつなぐ仕組み)の勧告タイミング — までは含みません。そこで MoAI は独自のステータスラインをターミナル下部に3行で表示し、マルチセッションランでは4行目まで付けて、「今トークンをどう使っているか」と「今どこで何をしているか」を一目で読めるようにします。
基本レイアウトは3行で、セッション名・バックログ観測があれば4行目(セッション行)が最後に条件付きで付きます。下の例は実際にレンダーされた出力の一例で、各セグメントが使うグリフ(glyph、小さな図形文字)までそのまま写しています。
🤖 Opus | 🧠 xhigh·t | ♻️ 87% | 🔅 v2.1.212 | 🗿 v3.1.3 | ⏳ 4h 52m | 💬 MoAI
🪫 CW: ███████░░░ 72% (⚠️/clear) | 🔋 5H: █████░░░░░ 56% (46m) | 🔋 7D: █░░░░░░░░░ 13% (May 28)
📁 moai-adk-go | 📡 modu-ai/moai-adk, 12/3 | 🅱️ main +2 | 💾 +0 M1 ?1 | 💌 PR #1234 (⌥approved)
🏷️ run | 👤 manager-develop | 🔄 TODO: 1/3- 1行目 — セッションが「どう」回っているか: モデル、推論深度、キャッシュ命中率、Claude Code バージョン、MoAI バージョン、セッション時間、出力スタイルを1行で示します。「このセッションがどの設定で回っているか」を即座に知らせます。
- 2行目 — 予算が「どれだけ」残っているか: コンテキストウィンドウ使用率(CW)と2つのローリング rate limit(5時間・7日)をゲージバーで示します。「今すぐ重い作業を回してよいか」を判断する根拠です。
- 3行目 — 今「どこで、何を」しているか: ディレクトリ、リポジトリとブランチ、開いている issue・変更リクエスト数、git 状態、アクティブな SPEC タスク、開いている PR のレビュー状態をまとめます。PR 中心のワークフローで最もよく目にする行です。
- 4行目(条件付き) — 誰として、どれだけ積もっているか: セッション名(
🏷️)、エージェント名(👤)、バックログ状況(🔄TODO: 進行中/待機)を示します。カンバンの同伴セッションのような名前付きセッションで自然に付き、観測元がなければそのセグメントは縮み、すべて空なら行自体が省略されます。セッション名を強調表示する場所でもあるので、ターミナルを複数開いてどの窓がどの役割か分からなくなったときの最初の手がかりになります。
statusline は単一のプログラムではなく、短いパイプラインです。Claude Code がレンダー周期ごとにセッション状態を JSON として渡し、MoAI がそれを受けて3行に加工してターミナルへ返します。
flowchart TD
A["Claude Code
(セッション状態を stdin JSON で渡す)"] --> B[".moai/status_line.sh
(shell wrapper — settings.json statusLine.command)"]
B --> C["moai statusline
(Go 単一バイナリ)"]
C --> D1["internal/statusline
(stdin JSON パース)"]
D1 --> D2["internal/statusline
(メモリ・メトリクス・git 収集)"]
D2 --> D3["internal/statusline
(3-line レンダー)"]
D3 --> E["ターミナル下部に3行表示"]なぜ shell wrapper が間に入るのでしょう? Claude Code の statusLine.command はコマンド文字列を1つだけ受け取ります。そこで .moai/status_line.sh が最小限のシェルラッパーとなって moai statusline バイナリを呼び出し、重い仕事(パース・収集・レンダー)はすべてコンパイル済みの Go バイナリの中で高速に処理されます。そのおかげで、レンダーごとに複数のプロセスを立てずに十分な情報を一度に描けます。
データ収集の段階では、stdin にない情報も補います。git 状態はローカルの git status --porcelain を直接パースし、MoAI バージョンはローカル設定から読み、アクティブなタスクはセッション状態ファイルから取ります。こうすると Claude Code が渡さない文脈まで1行に収められます。
1行目は「このセッションの設定と状態」を読む行です。モデル名はもちろん、Claude Code v2.1.139 から stdin に追加された effort/thinking 値で「どの推論深度で、拡張思考(thinking)が有効か」を示します。xhigh·t のようにレベルの後ろに ·t が付いていれば拡張思考が有効という意味で、この表示があればモデルポリシーが実際に適用されているかを一目で点検できます。
なかでもキャッシュ命中率はトークノミクスの中心的な指標です。cache_read トークンを (cache_read + cache_creation) で割った値で、常にロードされる指針を減らせばこの数字はすぐ上がります。逆に毎ターン大きなファイルを新しく読んだり指針ツリーが急に変わったりすると下がります。命中率が低く出るときは、どの変更がキャッシュを削っているかを追う手がかりになります。
データが足りないときは、値を作り上げず静かに隠します(graceful degradation)。キャッシュ生成トークンが0、または両方の値が0なら命中率セグメントを表示しません。この謙虚な省略が「ない数字で偽りの確信」を与えることを防ぎます。
2行目は3つのゲージバーで構成され、それぞれ意味が違います。
- CW(コンテキストウィンドウ): 現在のセッションが窓をどれだけ埋めたかを示します。バーの色は緑から黄、赤へ続く連続グラデーションで、先頭のバッテリーグリフは表示パーセンテージが70%を超えると「弱いバッテリー」標識に変わります。窓がいっぱいになると SSE ストールのリスクが高まるので、このゲージは「いつセッションを切り替えるか」の最初の信号です。
- 5H(5時間ローリング): 直近5時間の rate limit 消費率です。リセット時刻を併せて示し、「上限が解けるまでどれだけ待つか」を知らせます。
- 7D(7日ローリング): 直近7日の rate limit 消費率です。週単位の予算がどれだけ残っているかを見積もらせます。
サブスクリプション料金プランのユーザーにとって 5H/7D バーは事実上の予算ゲージです。この2本を見れば、「今すぐ重い作業を回すか、それとも費用削減のために CG モードで GLM ワーカーに任せるか」を合理的に決められます。CW バーがいっぱいで 5H バーも高ければ、セッションを止めてハンドオフでつなぐ方が費用と安定性の両面で有利です。
3行目は作業の文脈をまとめます。ディレクトリ、リポジトリとブランチ(開いている項目数と汚れたファイル数を含む)、git 状態、アクティブな SPEC タスク、開いている PR のレビュー状態が1行に入ります。
リポジトリとブランチは1つの統合セグメントとしてレンダーされます。owner/name 部分は Claude Code v2.1.145 から stdin に追加された workspace.repo から来て、その後ろにこのリポジトリで開いている issue 数と変更リクエスト数が , 12/3 のようにカンマとスラッシュで付きます。ブランチはローカル git から読みます。リポジトリ表示には 📡 グリフが付き、ブランチとは ASCII パイプ(|)でつながれて、2つの値が合わさると「どのリポジトリのどのブランチで作業しているか」が一目で入ります。ワークツリー(つながれた別の作業ディレクトリ)で作業中のときはブランチの前に [WT] 標識が付き、通常のチェックアウトと区別されます。
リポジトリ名のすぐ後ろに、このリポジトリで開いている issue 数と変更リクエスト数が , 12/3 の形のスラッシュペアで付きます。前の数字が開いている issue、後ろの数字が開いている変更リクエストです。以前は4行目に別置きされていた値ですが、今は対象リポジトリと同じ場所に付き、「この数字はどのリポジトリのものか」を位置で語ります。
1行に置けるスラッシュペアは1つだけです。この場所には以前リモートに対する先行/遅行のコミット数があり、その隣に開いている項目数がもう一つのペアとして並んだ結果、ブランチの 59/0 を「issue 対 変更リクエスト」と読み違える事故が実際に起きました。そこでこの場所は、ステータスバーで実際に目を留める値 — 開いている issue と変更リクエスト — に譲り、先行/遅行はどこにもレンダーされなくなりました。データ自体は今も収集していますが、バー上の置き場所がありません。
ペアは4つの状態で読みます。
| 表示 | 意味 |
|---|---|
, 12/3 | 取得できました — 開いている issue 12件、変更リクエスト 3件 |
, 0/0 | 取得できて、開いている項目が本当にありません |
, -/- | 尋ねる先はあるのに、今は値が分かりません — キャッシュが無い・読めない、または CLI が答えられませんでした(レート制限・認証・ネットワーク)。次の更新で解消しうる状態です |
| ペアもカンマも無い | 尋ねる先が無いか、尋ねるなと指定されています — 原因はすぐ下の5つ |
ペアがまるごと消える原因は5つあります。
segments.github: false— セグメントをオフにしています。statusline.forge: none(またはoff) — 数えないことを明示しています。statusline.forgeに認識できない値が書かれています(打ち間違い)。警告は出ず、消えたペアそのものが、今書いた値へ辿り着くための症状です。originリモートが自動判別できる公開ホストではありません — セルフホストのインスタンス、または origin 自体がありません。下のforgeキーを明示すれば解決します。- 該当する CLI(
ghまたはglab)が PATH にありません。
0 をそのまま出すことと、不明なときに 0/0 を使わないことは、どちらも意図した設計です。0 を出せばペアの形が常に同じなので、静かなリポジトリでも数字が生きていることが分かります。逆に取得に失敗したときに 0/0 を出すと、静かな取得失敗が「開いている項目なし」として報告されてしまいます — これだけは起きてはならない読み違いなので、不明には -/- という自分の形を与えました。データが無いことは 0 の証拠ではありません。
そして -/- は自力で解消しうる状態にだけ使います。この印は「尋ねる先があり、答えも来る途中だ」という約束です。答えの来る道が最初から無い側に同じ印を付ければ、いくら待っても消えない故障を探し回らせることになります。オフを選んだ人は何かを待っているわけではありません — そのため statusline.forge: none は、かつてのように -/- を出し続けるのではなく、ペアをまるごと下ろします。セグメントをオフにしたとき -/- すら出さない理由も同じです。
この抑制は一度掛かったら固まる値ではありません。更新の子プロセスが走るたび(TTL 10分)に最初から判定し直すので、gh をインストールする、あるいは forge の値を直せば、設定を触り直さずセッションを再起動もせずにペアが自然と戻ります。
タイミングには一つ違いがあります。CLI の不在とホストの未認識は更新の子プロセスが判定するため、最初の子がキャッシュを書く前のレンダーでは -/- が一瞬見えることがあり、子が走った次のレンダーで解消します(数秒)。一方 forge: none と明示した場合にはその区間が無く、最初のレンダーからペアが消えます。
開いている項目数をどこから数えるかは、statusline.yaml の statusline.forge キーが決めます。
statusline:
forge: gitlab # github | gitlab | none| 値 | 動作 |
|---|---|
| 未指定(デフォルト) | origin リモートのホストで判別します — github.com なら gh、gitlab.com なら glab |
github | 常に gh で数えます |
gitlab | 常に glab で数えます |
none (または off) | 数えません — ペアがまるごと消えます(-/- ではありません) |
| その他の値 | 判定へ戻らず数えません — ペアがまるごと消えます |
最後の行が重要です。打ち間違えたときにホスト名が示唆する側で静かに数えてしまうと、誤った数字が正しく見えます。そのため認識できない値は、数字でも -/- でもなく消えたペアとして現れます。警告メッセージは出ず、その不在が症状となって、たった今書いた設定値へすぐ辿り着けます。
セルフホストのインスタンスは名前だけでは区別できません — 社内 GitLab の git.example.com と社内 GitHub Enterprise はアドレスの形が同じです。そこで公開ホスト2つだけを自動判別し、それ以外は推測せずにこのキーを待ちます。
該当する CLI(gh または glab)が PATH に無ければ、数字が無いだけでエラーではありません — ペアは行からまるごと消え、最後にキャッシュされた値は後で戻すためにディスクへ残り、行の残りはそのまま描かれます。後から CLI をインストールすれば、次の更新でペアは自然に戻ります。GitHub 側は合計を一度に尋ね、GitLab 側は1ページ(最大100件)だけ列挙するので、開いている項目がそれより多いとページ数で止まります。
git 状態はどの状態でも同じ 💾 グリフを付け、その後ろに +ステージ M修正 ?未追跡 の個数が続きます。以前は状態ごとに別のメールボックスグリフを使っていましたが、細かい状態は後ろの数字がすでに語るので、先頭のグリフは1つに統一されました。
PR セグメントはレビュー状態を色で分けます。approved は緑、pending は黄、changes_requested は赤、draft はグレーで表示され、レビュー待ちの PR の状態を色だけで把握できます。MoAI ワークフローではすべての SPEC が plan-PR → run-PR → sync-PR サイクルを作るので、PR 状態を常に表示しておけば次の一手を決めるのに直接役立ちます。
CW バーの横に付くマーカーは、statusline が送る最も重要な勧告です。コンテキスト使用量がモデル別のしきい値を超えると2段階で点きます。soft 段階は「可能ならセッションを切り替えて」という勧告で、hard 段階は「今すぐ切り替えて」という上位の信号です。
flowchart TD
A["コンテキスト使用率の測定
(raw 使用量基準)"] --> B{"ウィンドウサイズクラス"}
B -- "1M コンテキスト
(Opus 5, GLM-5.3)" --> C{"使用率 50% 以上?"}
B -- "200K / 256K 標準
(Sonnet, Haiku, Fable)" --> D{"使用率 90% 以上?"}
C -- "いいえ" --> N["マーカーなし
(安全区間)"]
D -- "いいえ" --> N
C -- "はい" --> S["soft マーカー (⚠️/clear)
勧告"]
D -- "はい" --> S
S --> H{"auto-compact 認識
天井に到達?"}
H -- "いいえ" --> KEEP["soft のまま"]
H -- "はい" --> HD["hard マーカー (🛑/clear!)
上位信号"]
HD --> CLR["進行状況の保存 →
paste-ready resume → /clear"]
S --> CLRしきい値がモデルクラスごとに違うのは、窓が大きいほど早めに切り替える方が SSE ストール予防に有利だからです。1M コンテキストモデルは半分(50%)まで埋まったとき、200K/256K モデルは90%まで埋まったときに soft マーカーが点きます。hard マーカーは auto-compact が作動する時点を先取りして織り込んだ天井です。ただしランタイムの auto-compact がしばしばこの天井を先取するので、hard 段階は実際にはまれに発火する上位信号です。
マーカーが点いたら、決まった順序に従えばよいです。進行中の作業を progress.md に保存し、オーケストレーターが作った paste-ready resume メッセージを受け取ってから /clear でセッションを空にし、そのメッセージを新しいセッションに貼り付けて続けます。この流れはセッションハンドオフ規則と一致します。
ひとつ注意点があります。GLM-5.3 は実際には 1M コンテキストモデルですが、Claude Code はプロバイダに関係なく Claude スロット基準(Opus=1M, Sonnet/Haiku=200K)で context_window_size を報告します。そのため GLM セッションでは元の観測値が約 180K と誤って出ることがあります。MoAI は2か所でこの値を正します — ランチャーが CLAUDE_CODE_MAX_CONTEXT_TOKENS 環境変数でセッションに 1M ウィンドウを宣言し、ステータスラインは internal/statusline/memory.go の ResolveGLMContextWindow で観測値を補正します。glm-5.3 と glm-5.3-flash(デフォルトモデル)はそれぞれ独自のテーブルエントリで 1,000,000 にマッピングされ、MOAI_STATUSLINE_CONTEXT_SIZE 環境変数で直接上書きしたり、llm.glm.context_windows テーブルで設定したりもできます。GLM セッションでは元の値ではなく、MoAI ステータスラインの CW% を信頼してください。
ステータスラインはレンダーのたびに観測値を .moai/state/context-usage/<session-id>.json にも記録します。セッションごとに 1 ファイル、そのセッションの名前で残るので、同じプロジェクトで複数のセッションが動いても互いに上書きしません。この記録は、次のセッションが始まるときに「直前に窓がどれだけ埋まっていたか」を読む根拠に使われます。raw_pct(生の使用率)と stage(none/soft/hard)が主要なフィールドで、そのセッションが実際に動かしているモデルと effort も併せて入ります。session_id, writer_pid, captured_at もそのまま残ります。
なぜセッション区別が要るのでしょう? ひとつの作業ディレクトリを複数のセッションが共有するとき、あるセッションが別のセッションの使用量を引き継いで「窓がいっぱいだ」と誤判断してはいけません。そこで記録を書いたセッションの身元を確認し、一致しない・古い記録は無視して元の観測値にフォールバックします。目的は保守的に振る舞うことであって、欠けた値で偽りの確信を与えることではありません。
セグメントは .moai/config/sections/statusline.yaml でオン・オフします。各行が1つのセグメントトグルです。
statusline:
theme: catppuccin-mocha # カラーテーマ
forge: gitlab # github | gitlab | none (未指定 = origin のホストで判別)
segments:
# 1行目
model: true
effort_thinking: true
cache_hit: true
claude_version: true
moai_version: true
session_time: true
output_style: true
# 2行目
context: true
usage_5h: true
usage_7d: true
# 3行目
directory: true
git_branch: true # リポジトリ+ブランチ統合
git_status: true
task: true
pr: true
worktree: false # 既定は有効 — この行が無効化する例
github: true # 開いている issue/変更リクエストのペア (リポジトリセグメントの接尾辞)
# 4行目 — セッション行 (デフォルトでオン。明示しなくてもレンダーされます)
session: true # 🏷️ セッション名 + 👤 エージェント
backlog: true # 🔄 TODO: 進行中/待機16個のキーが正式な設定スキーマです。リポジトリを意味する owner/name 部分は git_branch セグメントの中で一緒にレンダーされる17番目の要素で、スキーマ外なので個別トグルはありません。github キーは名前こそそのままですが、今は3行目のリポジトリセグメントの issue/変更リクエスト ペアをオン・オフし、どのホスティングサービスに尋ねるかは上の forge キーが決めます。上の例に書かれた19個のトグルのうち16個がこのスキーマで、残る3個(github·session·backlog)はスキーマの外にあるトグルです。この3キーは設定に書かなくてもデフォルトでオンとしてレンダーされます — 観測元(セッション名、バックログキュー、forge キャッシュ)がなければ該当セグメントは静かに省略されます。過去の名前付きプリセット(full/compact/minimal)は廃止されたので、好みの組み合わせはセグメント単位で直接オン・オフします。
更新間隔は settings.json の statusLine.refreshInterval(単位: 秒、デフォルト値 10)で決めます。ステータスラインの設定ファイルではなく Claude Code ランタイム設定に当たります。間隔を短くしすぎると CPU 負担が増え、長くしすぎるとコンテキスト使用率の変化が遅く反映されます。通常はデフォルト値で十分です。
PR が出ないなら 3点を確認します。Claude Code が v2.1.145 以上である必要があります(stdin に pr フィールドが入るのはこのバージョンから)。現在のブランチに開いた PR があるか gh pr view で確認します。設定で pr: false と明示されていないかも見ます。
ハンドオフマーカーが出ないなら たいてい正常です。1M モデルで 50% 未満、200K/256K モデルで 90% 未満なら、まだしきい値に達していません。しきい値を超えているのに出ないなら、モデルのウィンドウサイズが正しくマッピングされているか(特に GLM 補正)を確認します。
色が出ないなら ターミナルが ANSI 256-color に対応しているか、NO_COLOR=1 が設定されていないか、テーマが環境に合っているかを確認します。
実際の出力を確認したいなら サンプル stdin をパイプで渡して、ステータスラインを一度描いてみられます。moai statusline コマンドにセッション状態を含む JSON 文字列を標準入力として与えると、ターミナルに印字される3行がそのまま出ます。この方法で、設定変更がレンダーにどんな影響を与えるかをレンダリングなしに点検できます。
Claude Code 2.1.169 以上では、プロンプトキャッシュを保持したままセッションの作業ディレクトリを変える /cd <path> コマンドを使えます。ステータスラインのディレクトリ表示は新しいパスに更新されますが、それまで積んだ推論コンテキストは積み直しません。新しいターミナルセッションを開く代わりにキャッシュを活かしておく方法と考えればよいです。セッション途中でコンテキストを失わずに作業ディレクトリだけ移したいとき(例: 作業中にワークツリーへ切り替える)に最も手間の少ない選択です。resume パターンとの連携はセッションハンドオフを参照してください。
- Settings JSON — Claude Code
statusLineフィールドの設定