Claude Media
subagentStatusLineでClaude Codeのサブエージェント行を書き換える

subagentStatusLineでClaude Codeのサブエージェント行を書き換える

subagentStatusLineは、エージェントパネルに並ぶサブエージェントの行を自作コマンドで描き換える設定です。設定の書き方、tasksのフィールド、動かない条件を扱います。

subagentStatusLineは、プロンプトの下のエージェントパネルに並ぶサブエージェントの行を、自作のコマンドで描き換える設定です。既定の行はname · description · token countの並びです。これを「種類ごとの色」や「コンテキスト使用率つき」の表示に差し替えられます。

設定したのに行が変わらない原因の多くは、スクリプトではなく管理設定のゲートにあります。

subagentStatusLineは何を変える設定か

Claudeがサブエージェントを動かすと、Claude Codeはプロンプトの下のパネルに1サブエージェント1行で一覧を出します。subagentStatusLineは、その行の本文を自分のコマンドの出力に置き換えます。画面下部のstatusLineとは別の設定で、対象はサブエージェントの行だけです。

設定はtypeを"command"にして、commandにスクリプトのパスかインラインのシェルコマンドを書きます。

{
  "subagentStatusLine": {
    "type": "command",
    "command": "~/.claude/subagent-statusline.sh"
  }
}

置き場所はユーザー設定、プロジェクト設定、ローカル設定のどれでも構いません。ただし、管理設定の制限がかかる組織では、後述のとおり自分の設定が動かないことがあります。

ステータスライン本体の設定項目はClaude Code statuslineの設定と表示項目の選び方にまとめています。ここでは、サブエージェント用の枠だけを扱います。

コマンドが受け取る入力と返す出力

コマンドは更新のたびに1回走り、見えているサブエージェントの行を1つのJSONオブジェクトにまとめて標準入力で受け取ります。入力は3つの部分でできています。

  • フック共通の入力フィールド(session_id、cwdなど)
  • 使える行幅を表すcolumns
  • 行ごとに1要素のtasks配列

返す側は、書き換えたい行ごとに1行のJSONを標準出力へ書きます。形は次のとおりです。

{"id": "<task id>", "content": "<行の本文>"}

contentはそのまま描画されます。ANSIの色指定も、OSC 8のハイパーリンクも使えます。返し方で挙動が変わる点は3つです。

返し方行の扱い
idとcontentを返す行の扱いその行をcontentで置き換える
contentを空文字にする行の扱いその行を隠す
idを省く、またはその行を返さない行の扱い既定の描画のまま残る

つまり、一部の行だけを書き換えて残りは既定のままにできます。全行を返す必要はありません。

tasks配列のフィールド

tasksの1要素が、1つのサブエージェント行に対応します。任意のフィールドは値がないとき省かれるので、スクリプト側で欠落を前提にします。

フィールド型内容
id型文字列内容タスクの識別子。返す行のidにそのまま使う
name型文字列(任意)内容サブエージェントに付いた名前。付いていないと省かれる
agentType型文字列内容Exploreやcode-reviewerのような種類。v2.1.293以降
status型文字列内容running、completed、failed、killedなど
description型文字列内容起動時にClaudeが付けた短い説明
tokenCount型数値内容実行中のトークン数。既定の行に出る値
model型文字列(任意)内容解決済みのモデルID。解決前は省かれる。v2.1.205以降
effort型文字列か数値(任意)内容設定された推論の努力レベル。v2.1.213以降
contextWindowSize型数値(任意)内容modelのコンテキストウィンドウ。v2.1.205以降

ほかに、type(値はlocal_agent)、進捗の要約を持つlabel、開始時刻のstartTime、直近16回分までのtokenSamples、サブエージェントの作業ディレクトリcwdがあります。labelは進捗の要約がなければdescriptionと同じ文字列になります。

見落としやすい点が2つあります。

  • effortは設定された値で、モデルが対応しないレベルのときは、Claude Codeが実際に適用する値と食い違うことがあります
  • agentTypeはフックのagent_typeと同じ値です。フックのmatcherで使っている名前を、そのまま表示の分岐に流用できます

tokenCountをcontextWindowSizeで割れば、行ごとのコンテキスト使用率になります。メインのstatusLine側のcurrent_usageとの違いはcurrent_usageで直近リクエストの内訳を読むで扱っています。

種類ごとに色分けする例

次のスクリプトは、agentTypeで表示を分け、tokenCountとcontextWindowSizeから使用率を出します。jq -cで1行1オブジェクトにしているのがポイントです。

#!/bin/bash
# ~/.claude/subagent-statusline.sh
jq -c '
  .tasks[] | {
    id,
    content: (
      (if .agentType == "Explore" then "\u001b[36m探索\u001b[0m"
       elif .agentType == "code-reviewer" then "\u001b[33m審査\u001b[0m"
       else (.agentType // "agent") end)
      + " " + (.name // .description)
      + " · " + ((.tokenCount / 1000 | floor | tostring) + "k")
      + (if .contextWindowSize
         then " (" + ((.tokenCount * 100 / .contextWindowSize | floor | tostring) + "%)")
         else "" end)
    )
  }'

chmod +xで実行権限を付けたうえで、設定のcommandに指定します。

動作は、手書きの入力をパイプで流して確かめられます。次の入力は、ステータスラインのページにあるフィールド定義に沿って自分で組んだ2行分の例です。1行目はname・model・contextWindowSizeあり、2行目はそれらがない行です。

cat > in.json <<'EOF'
{"columns":80,"tasks":[
 {"id":"t1","name":"scan-auth","type":"local_agent","agentType":"Explore",
  "status":"running","description":"認証まわりを探索","tokenCount":48200,
  "model":"claude-sonnet-5-5","contextWindowSize":200000},
 {"id":"t2","type":"local_agent","agentType":"code-reviewer",
  "status":"running","description":"差分レビュー","tokenCount":9100}
]}
EOF
~/.claude/subagent-statusline.sh < in.json

jq 1.7.1で実行すると、出力は次の2行になりました。

{"id":"t1","content":"\u001b[36m探索\u001b[0m scan-auth · 48k (24%)"}
{"id":"t2","content":"\u001b[33m審査\u001b[0m 差分レビュー · 9k"}

2行目はnameもcontextWindowSizeもないので、説明文と使用率なしの表示にフォールバックしています。欠けたフィールドで落ちないことが、このスクリプトの肝です。

色の指定が\u001bになっているのはJSONの決まりのためです。制御文字のESCは生のバイトでは文字列に入れられず、\u001bと書く必要があります。jqに組み立てさせれば自動でこの形になります。echoで自前のJSONを組むなら、ここで引っかかります。

幅・状態・進み具合を使う例

tasksには、色分けのほかにも使えるフィールドがあります。次のスクリプトは3つを組み合わせます。

  • columnsで本文を切り詰め、行が折り返さないようにする
  • statusで色を変える(failedは赤、completedは緑、killedは灰)
  • tokenSamplesの先頭と末尾を比べ、トークンが増えている行に↑を付ける

descriptionの代わりに、進捗の要約を持つlabelを優先して出します。labelは要約がなければdescriptionと同じ文字列なので、//でのフォールバックは要りません。

#!/bin/bash
jq -c '
  .columns as $w |
  .tasks[] | {
    id,
    content: (
      (.label // .description)[0:($w - 4)] as $text
      | (if .status == "failed" then "\u001b[31m"
         elif .status == "completed" then "\u001b[32m"
         elif .status == "killed" then "\u001b[90m"
         else "" end) as $c
      | (if (.tokenSamples | length) > 1
            and .tokenSamples[-1] > .tokenSamples[0]
         then " ↑" else " →" end) as $trend
      | $c + $text + (if $c != "" then "\u001b[0m" else "" end) + $trend
    )
  }'

columnsを20にした手書き入力で実行すると、長い説明は切り詰められ、failedの行は赤の指定つきでlabelが出ました(jq 1.7.1)。

{"id":"t1","content":"認証まわりのコードを順に読んで構 ↑"}
{"id":"t2","content":"\u001b[31m差分を確認中\u001b[0m →"}

切り詰めは先に本文だけに対して行い、そのあとで色の指定を足しています。色の指定を含めた文字列を切ると、ESCの途中で切れて表示が崩れます。また、jqの切り出しは文字数で数えるため、全角文字が多い行はcolumnsより幅が広くなります。余裕を持って短く切るのが無難です。

tokenSamplesは直近16回分までなので、長く走るサブエージェントでは「ここ最近の増え方」を表します。全体の傾きを見たいときは、tokenCountをcontextWindowSizeで割った使用率のほうが向いています。

行の本文にはOSC 8のハイパーリンクも使えます。サブエージェントの作業ディレクトリはcwdで渡されるので、ワークツリーで分離して動かしているときは、行からそのディレクトリを開けるリンクにしておくと探す手間が減ります。

経過時間を出して、終わった行を隠す例

startTimeはエポックからのミリ秒です。現在時刻との差を取れば、各サブエージェントが何分走っているかを行に出せます。空のcontentを返すと行が隠れるので、completedの行を畳む使い方もできます。

#!/bin/bash
jq -c '
  now as $now |
  .tasks[] | {
    id,
    content: (
      if .status == "completed" then ""
      else ((.label // .description) + " · "
        + ((($now - .startTime / 1000) / 60 | floor | tostring) + "分"))
      end
    )
  }'

400秒前に開始したrunningの行とcompletedの行を渡すと、出力は次のとおりでした(jq 1.7.1)。

{"id":"a","content":"調査 · 6分"}
{"id":"b","content":""}

nowは秒、startTimeはミリ秒なので、1000で割って単位をそろえています。ここを忘れると、経過時間が桁違いの数字になります。

動かないときに見る3つの条件

設定したのに既定の行のままなら、スクリプトより先に、次の3つのゲートを疑います。statusLineと同じ条件がsubagentStatusLineにもかかります。

手順

コマンドが実行されるまでの条件

  1. 1

    フォルダを信頼しているか

    statusLineと同様に、シェルコマンドを実行する設定なので、フックと同じワークスペースの信頼ルールに従います。信頼ダイアログを承認する前は動きません。

  2. 2

    disableAllHooksが立っていないか

    管理設定がdisableAllHooksを立てていると、オフになります。管理設定以外のファイルでtrueのときは、管理設定にある値だけが動きます。

  3. 3

    allowManagedHooksOnlyで絞られていないか

    allowManagedHooksOnlyが有効だと、statusLine、fileSuggestion、subagentStatusLineは管理設定の値だけが読まれます。

3つ目と、2つ目の「管理設定以外でtrue」の場合は、管理設定に値があればそれが動き、なければ自分の値が警告なしで読み飛ばされます。claude --safe-modeで起動したときも同じ扱いです。エラーも出ないので、設定を書いた本人には「何も起きない」としか見えません。

プラグインは、settings.jsonに既定のsubagentStatusLineを同梱できます。ただし、フックと違って、プラグインの値はallowManagedHooksOnlyの下では動きません。管理設定のenabledPluginsで強制有効にしたプラグインでも同じです。組織で配るプラグインにこの設定を載せても、allowManagedHooksOnlyの環境では動かないということです。

allowManagedHooksOnlyがstatusLineやファイル提案まで巻き込む連鎖は、allowManagedHooksOnlyでhookを組織限定にする際の連鎖的な影響に詳しくあります。

statusLineとの違い

名前が似ていますが、受け取るJSONも返す形も違います。

観点statusLinesubagentStatusLine
表示場所statusLine画面下部のステータス行subagentStatusLineプロンプト下のサブエージェントの行
入力の中心statusLineセッション全体のデータ(モデル、コンテキスト、コストなど)subagentStatusLinetasks配列(サブエージェントごと)
出力statusLine標準出力の文字列(複数行可)subagentStatusLine行ごとに{"id","content"}のJSON
既定に戻す方法statusLine設定を消すsubagentStatusLineidを省く、または行を返さない

設定ページに載っているsubagentStatusLineのキーはtypeとcommandです。statusLineにあるpaddingやrefreshIntervalは、このキーの項目としては載っていません。

よくあるつまずき

  • agentTypeが空になる: v2.1.293より前のバージョンでは、agentTypeはペイロードにありません。// "agent"のようなフォールバックを書いておけば、古い環境でも表示が崩れません。このバージョンの追加はClaude Code v2.1.293のリリースノートにあります
  • 行が書き換わらない: 出力が1行ずつ正しいJSONになっているかを、jq .に通して確かめます。ESCを生のバイトで入れた行は、JSONとして成立しません
  • 使用率が出ない: modelが解決される前の行には、contextWindowSizeも付きません。両方の欠落を前提に分岐を書きます
  • 終了した行が見当たらない: 成功したバックグラウンドサブエージェントの行はすぐに消え、フッターに/tasks to see subagentsが30秒出ます。失敗した行や自分で止めた行は30秒残り、選んでxを押すと早く消せます。statusで色を変えていても、成功の緑が見える時間はほぼありません。完了後の確認は/tasksコマンドでサブエージェントの完了を確認するが参考になります

使いどころの目安

向いているのは、サブエージェントを何本も並行させる使い方です。descriptionとtokenCountだけの行では、どれがレビュー担当でどれが調査担当か、どのくらいコンテキストを食っているかが読み取りにくくなります。種類で色を分ける、使用率の高い行だけ赤くする、といった工夫でパネルが一覧表として機能し始めます。

サブエージェントを1本ずつ順番に動かす程度なら、既定の行で足ります。モデルの割り当てを設計する段階なら、行にmodelとeffortを出せば設計どおりに動いているかを見張れます。割り当て設計の考え方はClaude Codeサブエージェントのモデル配分設計にあります。

この記事を共有:XはてブLinkedIn