Claude Code bash_command_class — Bashの種別をトレースで集計する
bash_command_classはBashの先頭プログラムをvcsやpackage_managerなどの固定カテゴリで記録するトレース属性です。コマンド本文を取らずに傾向を見る方法をまとめます。
Claude Codeが実行するBashコマンドの傾向を、コマンド本文を送らずに集計したいなら、見るのはbash_command_classです。Bashの先頭プログラムをvcsやpackage_managerといった固定カテゴリに分けた値が、トレースのスパンに載ります。
先に落とし穴を書いておきます。この属性はclaude_code.toolスパンの属性で、tool_resultなどのイベントには付きません。「トレースとイベントの両方で集計する」のではなく、トレースで種別を出し、イベントとはtool_use_idでつなぐ形になります。この記事は、有効化の手順、属性の読み方、集計の切り口、つなぎ方を順に並べます。
bash_command_classは何を記録する属性か
bash_command_classは、Bashツールの呼び出しについて「コマンドの最初のプログラムが、固定リストのどのカテゴリに入るか」を示す文字列です。公式の属性表に載っている値は次の3種類の書き方です。
| 値 | 意味 |
|---|---|
vcs、package_managerなど | 意味先頭プログラムが固定リストにあるときのカテゴリ |
other | 意味先頭プログラムが固定リストの外にある |
unparsed | 意味コマンド行を解析できなかった |
付くスパンはclaude_code.toolで、Claude Code v2.1.268以降が必要です。専用の有効化スイッチはなく、属性表の「Gated by」欄も空です。つまりOTEL_LOG_TOOL_DETAILSを立てなくても出力されます。
隣にあるbash_argv0とtool_name_safe
同じclaude_code.toolスパンには、似た名前の属性が2つ並びます。
bash_argv0: 先頭プログラムが同じ固定リストにあるとき、そのプログラム名(git、npmなど)。リスト外はothertool_name_safe: ユーザーが付けた名前を含まない形のツール名。組み込みツールはそのまま、MCPツールはmcp_otherになる(一部の固定形のツールを除く)
どちらもbash_command_classと同じv2.1.268以降です。bash_command_classが「何系の作業か」を、bash_argv0が「具体的にどのプログラムか」を答えます。たとえばgitとghが同じvcsに入るかどうかは公式が書いていないので、粒度を知りたければbash_argv0を一緒に出力して照らします。
コマンド本文を取らずに傾向を見る
Bashコマンドの中身を送る属性は別にあり、こちらは明示的に有効化したときだけ出ます。
| 属性 | 載る場所 | 条件 |
|---|---|---|
full_command | 載る場所claude_code.toolスパン | 条件OTEL_LOG_TOOL_DETAILS=1 |
bash_command | 載る場所tool.outputスパンイベント | 条件OTEL_LOG_TOOL_DETAILS(さらにOTEL_LOG_TOOL_CONTENT=1でイベント自体が出る) |
bash_command、full_command | 載る場所tool_result・tool_decisionイベントのtool_parameters | 条件OTEL_LOG_TOOL_DETAILS=1 |
公式は、ツールの入力引数が機微な値を含むことがあるので、有効にするならバックエンド側でフィルタや秘匿をかけるよう書いています。これに対してbash_command_classは、カテゴリ名だけの粗い値です。コマンドの引数、パス、トークンが入る余地がありません。
そのため、次のような使い分けができます。
- 全社の既定:
OTEL_LOG_TOOL_DETAILSは立てず、bash_command_classだけで傾向を見る - 個別調査: 問題が出たチームやセッションに限って
OTEL_LOG_TOOL_DETAILS=1を足し、full_commandで中身を確かめる
otherが多いときだけ詳細ログを足す、という二段構えが取れます。
有効化の手順
トレースはベータ機能で、既定では出ません。次の2つの変数を両方立て、OTEL_TRACES_EXPORTERで送信先の種類を選びます。
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
claudeエンドポイント、プロトコル、ヘッダー、mTLSは、ログやメトリクスと共通のOTLP設定を引き継ぎます。トレースだけ別の宛先にしたいときはOTEL_EXPORTER_OTLP_TRACES_ENDPOINTが使えます。スパンの送信間隔はOTEL_TRACES_EXPORT_INTERVALで、既定は5000ミリ秒です。
管理設定(managed settings)があるマシンでは、開発者が設定したシグナル別の認証情報やエンドポイントを、Claude Codeが起動時に取り除くことがあります。トレースが届かないときは、管理設定側でOTLPの宛先が固定されていないかを先に疑います。詳細ベータトレーシングで送信先を切り替える変数の話は、BETA_TRACING_ENDPOINTの解説が別にまとめています。bash_command_classは通常のトレース設定で出るので、そちらの変数は要りません。
手元で値を確かめる
バックエンドを用意する前に、スパンを標準出力に流して属性を見られます。OTEL_TRACES_EXPORTERはconsoleも受け付けます。
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
export OTEL_TRACES_EXPORTER=console
export OTEL_TRACES_EXPORT_INTERVAL=1000
claude数ターン作業させたあと、出力からclaude_code.toolスパンのうちtool_nameがBashのものを探し、bash_command_class、bash_argv0、tool_use_id、duration_msの並びを見ます。git statusを実行させればbash_argv0がgitのスパンが、リスト外のスクリプトを実行させればotherのスパンが見つかるはずです。ここで出てくる値の一覧が、手元のバージョンの固定リストを知る手がかりになります。
集計の切り口は3つあれば足りる
属性は文字列1つなので、どのバックエンドでも「tool_nameがBashのスパンをbash_command_classで数える」形に落ちます。意味のある切り口は次の3つです。
- カテゴリ別の件数: 作業の内訳が分かります。
vcsが大半なら履歴操作の多い使い方、package_managerが目立てば依存の出し入れが多い使い方です。 otherの比率: 固定リストの外にあるプログラムの割合です。社内CLIやプロジェクト独自のスクリプトが多い環境では高く出ます。比率が高いほど、カテゴリだけでは中身が見えません。bash_argv0もotherになるので、中身を知るにはfull_commandの出番です。unparsedの比率: コマンド行を解析できなかった呼び出しの割合です。ゼロでなくても異常とは限りませんが、急に増えたときは、コマンドの書かれ方が変わったサインとして見る価値があります。
スパンにはagent_idとparent_agent_idも付きます(メインセッションでは不在)。サブエージェントが走らせたBashを分けて数えたいときは、これで絞れます。
件数より所要時間と成否で読む
スパンにはduration_msもあります。権限待ちと実行を合わせた壁時間なので、カテゴリ別の平均や上位パーセンタイルを出すと「どの種別が待たされているか」が分かります。ただし権限の承認待ちが含まれる点に注意が必要です。実行時間だけが欲しければ、子のclaude_code.tool.executionスパンを使います。
親子のスパンは、それぞれ次の値を持ちます。
| スパン | 主な属性 | 読み取れること |
|---|---|---|
claude_code.tool | 主な属性bash_command_class、duration_ms(権限待ちと実行の合計)、tool_use_id | 読み取れること何系のコマンドか、全体で何ミリ秒かかったか |
claude_code.tool.blocked_on_user | 主な属性duration_ms(権限判定の待ち時間)、decision(accept・reject・cancelled) | 読み取れること承認待ちにどれだけ使ったか、どう決まったか |
claude_code.tool.execution | 主な属性duration_ms(ツール本体の実行時間)、success、error_class、tool_use_id | 読み取れること実行にかかった時間と、失敗したか |
tool.executionとblocked_on_userにはbash_command_classが付きません。結び方は2つで、属性表の書き方が違います。tool.executionはtool_use_idが親のclaude_code.toolスパンと同じ値だと明記されているので、この値で結べます。blocked_on_userの属性表にはduration_ms・decision・sourceしかなく、tool_use_idはありません。こちらはスパンの親子関係(親スパンID)でclaude_code.toolに結びます。バックエンドのクエリ言語は問いませんが、形は同じです。
tool_nameがBashのclaude_code.toolスパンから、スパンIDとbash_command_classを取る- 親スパンIDがそのスパンIDと一致する
blocked_on_userのduration_msを結合し、bash_command_classごとに平均と上位パーセンタイルを出す - 承認待ちが長い種別が見つかったら、その種別を許可ルールの候補として見直す
SQLで書ける環境なら、2の結合はおおよそ次の形です。テーブル名とカラム名はバックエンドごとに違うので、模式として読んでください。
SELECT t.bash_command_class,
COUNT(*) AS calls,
AVG(b.duration_ms) AS avg_wait_ms
FROM spans t
JOIN spans b ON b.parent_span_id = t.span_id
WHERE t.name = 'claude_code.tool'
AND t.tool_name = 'Bash'
AND b.name = 'claude_code.tool.blocked_on_user'
GROUP BY t.bash_command_class
ORDER BY avg_wait_ms DESC;claude_code.toolのduration_msは権限待ちと実行の合計、tool.executionのduration_msは本体の実行時間だけです。この2つの差がおおよそ待ち時間にあたるので、blocked_on_userを結合しなくても「待たされている種別」の当たりは付けられます。
tool.executionスパンのsuccessとerror_classはtool_use_idで親スパンと結べます。bash_command_classとsuccessの組み合わせを数えれば、イベントを使わず、トレースだけで種別ごとの失敗率が出ます。ログのストアを別に持たない構成では、こちらのほうが手数は少なく済みます。
blocked_on_userのdecisionにはcancelledもあります。権限の判定を待っている途中でClaude Codeが待つのをやめ、そのツール呼び出しを実行しなかった場合の値です。承認率を出すときは、acceptとrejectだけを分母にするのかcancelledも含めるのかを先に決めておきます。
イベントとはtool_use_idでつなぐ
tool_resultイベントにはbash_command_classがありません。success、duration_ms、error_type、decision_sourceなどが載るだけです。種別ごとの失敗率を出したいときは、スパンとイベントを結合します。
つなぎ目はtool_use_idです。claude_code.toolスパンのtool_use_idは、tool_resultとtool_decisionイベントの同名の属性と一致します。公式の属性表にもそう書かれています。
| 知りたいこと | 使うもの | 結合 |
|---|---|---|
| 種別ごとの失敗率 | 使うものスパンのbash_command_class + tool_resultのsuccess・error_type | 結合tool_use_id |
| 種別ごとの拒否 | 使うものスパンのbash_command_class + tool_decisionのdecision | 結合tool_use_id |
| 拒否の理由 | 使うものsafety_eventのcategory | 結合tool_use_id |
結合はClaude Code側ではなく、ログとトレースを両方持つバックエンドで行います。トレースとログを別のストアに入れている場合は、tool_use_idをキーにクエリを書く手間が増えます。
拒否の側は、safety_eventをcategory別に集計する記事の手順がそのまま使えます。拒否された呼び出しにも、権限判定のあとにスパンが残るかどうかは公式の属性表に記載がありません。拒否側の結合を設計するときは、手元で拒否を起こして確かめるのが安全です。
落とし穴
- イベントには付かない:
tool_resultのtool_parametersにも出ません。イベントだけのパイプラインでは使えません。 - v2.1.268以降: 古いバージョンが混在する環境では、属性が無いスパンが含まれます。メトリクス側では
OTEL_METRICS_INCLUDE_VERSIONでapp.versionを付けられます。スパン側にバージョン系の属性が付くかは属性表に書かれていないので、手元のconsole出力で確かめてから集計に使います。 - トレースはベータ: 属性名や挙動が変わる可能性があります。ダッシュボードのクエリは、更新のたびに見直す前提で作ります。
- 対象はBashツール: 属性表の説明は「For the Bash tool」です。PowerShellツールで同じ属性が付くかどうかは記載がありません。
- 固定リストは非公開に近い: 前述のとおりカテゴリの全体像は属性表にないため、新しいカテゴリが増えても気づきにくくなります。凡例を固定せず、未知の値を「その他」に束ねる集計にしておくと崩れません。
周辺の集計との役割分担
bash_command_classが答えるのは「どんな種類のコマンドが実行されたか」だけです。モデルが応答を拒否した回数はapi_refusalイベントの数え方、監査の問いをどのイベントで引くかはセキュリティ監査とOTELイベントの対応表に分かれています。
Bashの権限判定そのものは、このテレメトリとは別の仕組みです。&&でつないだ複合コマンドがルールにどう照らされるかは、Bash権限ルールと複合コマンドの評価で扱っています。種別の集計は、その権限ルールを見直す材料にできます。たとえばpackage_managerの呼び出しが毎回承認待ちになっているなら、許可ルールの候補になります。
まとめ
bash_command_classは、OTEL_LOG_TOOL_DETAILSなしで出る粗いカテゴリ値です。全社の既定で傾向を見て、otherやunparsedが目立つ範囲だけ詳細ログに進む、という順序で使うと、コマンド本文を溜めずに済みます。成否や拒否と合わせたいときはtool_use_idで結合します。カテゴリの全リストが公式に載っていない点だけは、手元のconsole出力で補います。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。