Claude Codeのスキル本文に !`git diff --cached` と書くと、スキル起動時にコマンドが先に実行され、その出力が本文へ埋め込まれます。コミットメッセージ生成スキルをこの方式へ作り直し、サブエージェントのツール呼び出しをなくしました。
## Staged diff
```!
git diff --cached
```
この記事では、遅さの原因だったツール往復をどう特定し、どう設計し直したかを紹介します。
コミットメッセージ生成は軽い処理のはずだった
ラビーでは、Claude Codeのスキルやエージェント定義を LabeeHive/standards というリポジトリで一元管理しています。その中の commit-message スキルは、ステージ済みの変更から Conventional Commits 形式のメッセージを生成するもので、コミット前に /commit-message と打つだけで提案が返ってきます。
今年の2月に作った最初の版は、こういう構成でした。
---
name: commit-message
description: Generate commit messages following conventional commits.
model: haiku
context: fork
agent: general-purpose
allowed-tools: Bash(git:*)
---
モデルは最小の haiku、context: fork でメイン会話から切り離した使い捨てコンテキストで実行する。軽量にしたつもりでした。本文では生成手順をこう指示していました。
## Process
1. Run `git diff --cached --stat` to see staged files
2. Run `git diff --cached` to see actual changes
3. Analyze the changes
4. Generate appropriate commit message
ところが実際に使うと、メッセージ1行が返るまでに体感で数十秒待たされます。haiku を選んでいるのに、です。
遅さの原因はモデルではなくツール往復
フォークされたサブエージェントの動きを追うと、原因はモデルの速度ではありませんでした。上の手順だと、エージェントは1回目の推論で git diff --cached --stat を発行し、結果を受け取って2回目の推論で git diff --cached を発行し、その結果を読んでから3回目の推論でようやくメッセージを書きます。つまり1行の出力のために推論パスが最低 3回 走ります。各往復にはツール実行の許可判定やプロセス起動も挟まるため、モデルを速くしても待ち時間は縮みません。
もうひとつ、agent: general-purpose は起動時に CLAUDE.md を読み込みます。コミットメッセージの生成にプロジェクトの開発指示は不要なので、これも無駄なコンテキストでした。
動的コンテキスト注入で diff を先に渡す
Claude Code には dynamic context injection という仕組みがあります。スキル本文に !`コマンド` と書くと、本文が Claude に送られる前にコマンドが実行され、出力がその場所を置き換えます。Claude がツールとして実行するのではなく、ハーネス側の前処理です。Claude が受け取る時点では、結果が埋め込まれた完成形のプロンプトになっています。
この仕組みを使って、エージェントに取得させていたデータをすべて事前注入に置き換えました。
## Staged files
!`git diff --cached --stat`
## Staged diff
```!
git diff --cached
```
## Recent commits (style reference)
!`git log --oneline -15`
1行のコマンドは !`...` のインライン形式、複数行や出力が長くなるものは ```! で開くフェンスブロックを使います。エージェントが起動した時点で diff もスタイル参照用の直近コミットも本文に揃っているため、指示は「データはすべて注入済み。コマンドを実行しないこと」だけで済みます。allowed-tools: Bash(git:*) も外しました。ツールを持たないエージェントは、許可判定そのものが発生しません。
ついでにスタイル参照として git log --oneline -15 を注入したことで、リポジトリごとの scope の使い方など、既存コミットの慣習に寄せた提案が返るようになりました。旧版にはなかった入力です。
エージェントを Explore に切り替える
context: fork で実行するエージェント種別も general-purpose から Explore に変えました。公式ドキュメントによると、組み込みの Explore と Plan はコンテキストを小さく保つために CLAUDE.md と git status の読み込みをスキップします。フォークされたスキルが受け取るのは、SKILL.md 本文とエージェント自身のシステムプロンプトだけになります。
データは注入済みでツールも使わないのですから、読み取り専用の Explore で十分です。general-purpose を選んだままだと、生成のたびに CLAUDE.md のトークンを読み込む分だけ入力が膨らみます。
出力をコードブロック1つに固定する
context: fork ではサブエージェントの最終応答が、そのまま結果としてメイン側に返ります。応答に「この変更は〜なので」のような解説文が混ざると、提案されるコミットメッセージにその文章まで含まれてしまいます。
そこで出力の契約を固定しました。応答はフェンスコードブロック1個のみ、前後に文章を置かない、diff が空のときはブロックの中身を No staged changes. にする、という指定です。送信前のセルフチェックとして「コードブロックは1個か」「subject は50文字以内か」「type は判定表の最初に一致した規則か」の3項目を本文末尾に置き、生成のばらつきを抑えています。
作り直した結果、1回の生成にかかる推論はツール往復なしの1パスだけになりました。haiku の応答速度がそのまま体感になります。
運用するうえでの制限
この方式に切り替えるときに把握しておくべき制限がいくつかあります。
- 注入は元ファイルに対して1回だけ走ります。コマンド出力に
!`...`の形が含まれていても再展開はされません - インライン形式は
!が行頭か空白の直後にあるときだけ認識されます。KEY=の直後のように別の文字に続く位置では文字列のまま残ります settings.jsonで"disableSkillShellExecution": trueが設定されている環境では、コマンドは 実行されず 置換メッセージに差し替えられます。managed settings で配布されている組織では事前に確認が必要です- 注入コマンドはスキル起動のたびに走るため、重いコマンドを書くと起動そのものが遅くなります。diff が巨大なリポジトリでは注入サイズにも注意が必要です
スキル定義の現物は LabeeHive/standards に置いてあります。
GitHubGitHub - LabeeHive/standardsContribute to LabeeHive/standards development by creating an account on GitHub.