毎回打ち直す4つのプロンプトが4つのファイルになる
Claude Codeのextensionとは、同じプロンプトを打ち直さなくて済むようにリポジトリに置いておくファイルのことです。skill、hook、subagent、MCPサーバーの4種類があります。私たちは、JSONファイルをデータベースにした小さな請求書APIという実際のリポジトリで、繰り返し発生する4つの仕事を試しました。どの仕事も、最初は間違ったextensionを使い、その誤った選択は毎回画面上で失敗しました。そのあと、それぞれをコミット済みのファイルにしました。このページでは、4つの操作と、どの種類を使うかを決める選び方カードを紹介します。
以下のすべてを方向づける結果が2つあります。hookは保証だと言われ、メモリファイルのルールはお願いにすぎないと言われます。それなのに、最初のhookは実行され、警告も出たのに、書き込みは通ってしまいました。実行されるだけのhookを、本当にブロックするhookに変えるものは、hookの節で扱います。最初に接続したdocsサーバーは何も変えず、答えを返してくれたのはツール自身のdocsサーバーでした。多くの人がコピーするhookのレシピは別のスクリプトファイルも必要としますが、私たちのhookは設定ファイルの中だけで完結します。
4つのファイルのどれかを書く前に、セッションがすでに何を読み込んでいるかを知っておく必要があります。
4つの正体と、セッションがすでに読み込むもの
Claude Codeのextensionとは、同じ指示をプロンプト欄に打ち直さなくて済むように、リポジトリに追加するファイルです。種類は4つあり、すでに知っている人は次の節に進んでかまいません。
- skillは、リポジトリに置いた手順書です。Claudeはその仕事が出てきたときだけ手順を読み込み、名前を打って自分で起動することもできます。
- hookは、gitのコミットhookに似ていますが、Claude自身の操作に対するものです。編集などのイベントで実行されるスクリプトで、Claudeの意図に関係なく動きます。
- subagentは、何も置かれていない机に座る外部の担当者のようなものです。依頼内容を受け取り、自分で読み込みを行い、資料の山ではなくレポートを返します。
- MCPサーバーは、Claudeが呼び出せるAPIクライアントです。データベースやツールのdocsなど、ディスク上にないデータのために使います。
何かを測るには、まず基準値が必要です。新しいセッションを開いて、contextコマンドを実行してください。最初の一言の前に読み込まれているものがすべて表になって出力されます。私たちのベンチでは、合計は1mのウィンドウに対して26.2kトークンで、メモリファイルの行もMCPの行もまったくありませんでした。これ以降にClaudeへ任せる仕事は、すべてこの表に1行を足すか、そのままにするかのどちらかです。
1つ目の仕事: リリースのチェックリストはskillになる
skillは、専用のフォルダに置いたチェックリストで、Claudeは呼び出されたときだけ読み込みます。1つ目の仕事は、プロンプト欄に貼り続けているリリースのチェックリストです。型チェック、テスト、バージョン更新、changelogの1行、コミット、タグ付けの順です。
置き場所として真っ先に思いつくのはメモリファイルですが、これは間違った選択です。そのファイルの内容は、すべてのセッションのすべてのターンで読み込まれるからです。私たちは手順をそこに入れて、contextの表をもう一度見ました。Memory filesの行が210トークン分増えており、リリース作業をしていなくても読み込まれていました。docsにも同じことが書かれています。そのファイルの一節が手順にまで育ったなら、それはskillに属するということです。
正しい選択は、skillsディレクトリの下にフォルダを作り、手順を.claude/skills/release/SKILL.mdに書くことです。先頭に短い説明を置き、frontmatterに1行、disable-model-invocation: trueを足します。この行があると、skillはClaude自身の一覧から外れ、起動できるのは自分だけになります。コミットとタグ付けを行う仕事にはこれが望ましい挙動です。あとはスラッシュとその名前を打てば、チェックリストが走ります。
| contextの読み取り | 合計 | Memory filesの行 |
|---|---|---|
| 基準値、新しいセッション | 26.2k / 1m | なし |
| メモリファイルにチェックリスト | 26.4k / 1m | 210トークン |
| skillとしてのチェックリスト | 26.2k / 1m | なし、skillは一覧に載らない |
実行すると、Claudeは1分足らずで6つのステップをすべて完了しました。型チェック、34件のテスト、バージョンを0.4.0から0.4.1へ更新、changelogの1行、コミット、タグ付けです。私たちのチェックリストの欠陥にも気づきました。書いたとおりのコミットコマンドは、追跡されていないファイルを飛ばしてしまいます。そこでClaudeは、新しいchangelogを先にステージして、そのことを伝えてきました。これでリリースは、打つだけのコマンドになりました。リポジトリに入っているのでチーム全員が使え、打つまではcontextを1トークンも使いません。
2つ目の仕事: 守るべきルールはhookになる
hookは、ツールが動く前にClaude Codeが実行するスクリプトです。ここでは、あらゆる編集や書き込みの前に動きます。そしてそのスクリプトが何を返すかで、次に起きることが決まります。2つ目の仕事は、絶対に破れないルールです。このリポジトリでは、保存データのファイルは手作業で編集せず、すべての変更はマイグレーションを通します。
そのルールをメモリファイルに大文字で書いても、強制にはなりません。docsははっきり書いています。そのファイルはcontextであって、強制される設定ではありません。「never edit this file」のような指示はお願いであって保証ではなく、編集をブロックするhookこそが強制です。
最初に思いつくhookは、私たちが書いたものでした。設定ファイルの中で、データファイルへの書き込みを監視し、警告を表示して、コード0で終了します。ログ用のスクリプトのようなものです。そのうえでClaudeに、マイグレーションなしでファイル内の請求書の金額を直接変えるよう頼みました。hookは実行され、警告も表示されましたが、書き込みは通りました。3件目の請求書の単価が8000から12000に変わったのです。Claudeの返答にはhookのことが一切出てきませんでした。きれいに終了して何も出力しないhookは何も判断していないことになり、その警告はClaudeに届かないからです。
保証になるのは、hookが実行されることではなく、hookが返す答えです。修正では、同じhookのまま、denyの判定を返すようにします。Claudeに読ませたい理由を載せた、短いJSON1行です。コード2で終了しても、同じようにブロックできます。hook全体は.claude/settings.jsonの中の1つのインラインコマンドで、スクリプトファイルはありません。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
同じ依頼をすると、編集は拒否され、データファイルには手が触れられませんでした。Claudeには次のエラーが見えていました。PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). Claudeは何も変更していないと伝え、ガードレールを上書きするか、マイグレーションを書くかを尋ねてきました。
限界は、Claude自身が指摘したものです。このhookが監視するのは編集と書き込みのツールだけで、あとの実行では、Claudeがシェル経由で変更することを提案してきました。hookはシェルまで覆うか、シェルをallowリストに入れないでおく必要があります。扉を1つしか守らない防御は、結局お願いにすぎないからです。hookは会話の外で動くので、contextには何のコストもかかりません。
3つ目の仕事: あふれる監査はsubagentになる
subagentは、空のcontextを持つ、独立したClaudeのセッションです。依頼内容を受け取り、必要なものを読み込み、返ってくるのは要約だけです。3つ目の仕事は、スレッドをあふれさせる監査です。sourceフォルダ内のすべてのファイルをREADMEの規約と照らし合わせ、違反をファイルと行番号つきで全部挙げるというものです。
そのまま頼むと、Claudeはすべてのファイルを会話に読み込み、その内容はセッションの残りの間ずっとそこに残ります。私たちの実行では、Claudeは19個のsourceファイルをすべて読み、contextの表のMessagesの行が膨らみ、結果は山の下に埋もれました。
正しい選択は、.claude/agents/の下にファイルを置くことで、ここでは.claude/agents/conventions-auditor.mdです。名前、使うべき場面の説明、使用できるツール、固定したモデルを書きます。モデルを固定するのは大事です。固定しないと、subagentはセッションが動いているモデルをそのまま引き継ぎますが、読み込み中心の仕事の多くには大きなモデルは要らないからです。
まったく同じ質問をもう一度したところ、Claudeは自分でファイルを読みました。2回とも、agentは一覧にあったのに呼び出されませんでした。委任は2回中0回です。うまくいったのは、プロンプトの最初で名前を呼ぶことでした。「use the conventions auditor subagent」です。呼び出しは1回だけ行われ、短い要約が返ってきました。
| 実行 | contextの合計 | Messagesの行 |
|---|---|---|
| インラインの監査 | 46.1k | 19.3k |
| 名前を呼んだsubagent | 34.1k | 7.2k |
見つかった内容の質も同等でした。subagentは、インラインの実行が見逃したパスのバグを見つけ、インラインの実行が見つけたものを1つ見逃しました。つまり、ファイルがsubagentを存在させ、プロンプトがClaudeにそれを使わせるのです。Claudeが自分で選ぶようになるまでは、名前を呼んでください。1つ限界があります。料金は変わりませんでした。どちらの実行も同じコストです。読み込み自体は行われていて、それがウィンドウの中で起きないだけだからです。
4つ目の仕事: Claudeが届かないデータはMCPサーバーになる
MCPサーバーは、Claudeが呼び出せるAPIクライアントで、その役目はモデルより新しいデータや、ディスクの外にあるデータを扱うことです。ここでの反射的な選択は間違いなので、私たちはまずそれを選びました。
テスト対象のライブラリの公開docsサーバーを接続し、Claudeにfake timerを使ったテストを頼みました。何も変わりませんでした。Claudeはそのライブラリをすでに知っていて、私たちが名前を出したときだけサーバーを呼びました。モデルがすでに知っているdocsは、モデルが届かないデータではありません。
本当の試験は、Claude Code自身についての質問、モデルより新しいhookの設定についての質問です。hookが失敗したときにブロックする設定はどれか、どのバージョンで追加されたか、正確なJSONは何か、という内容です。サーバーなしでは、Claudeは自分のインストールの設定スキーマを読み、何も見つけられず、その設定は見つからないので推測はしない、と答えました。誠実ですが役に立たない答えです。その設定は存在し、モデルの知識より後に追加されたものだからです。
解決策は、Claude Code自身のdocsサーバーをリポジトリに追加することです。project scopeを使えば、設定がリポジトリのルートのファイルに入り、チーム全員に届きます。
claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp
これで.mcp.jsonが書かれます。Claude Codeは、次のセッションで承認するまでサーバーをpendingと表示し、そこからはそのツールがClaudeから使えるようになります。サーバーの名前を出さず、普通の言葉で同じ質問をしました。Claudeはdocsを検索してページを見つけ、設定、バージョン、追加するJSONをリンクつきで答えました。
その設定は"onFailure": "block"で、失敗したhookが操作を素通しにせず、ブロックするようにします。追加されたのはv2.1.295で、私たちのインストールはまだ2.1.294でした。これが限界です。Claudeは、私たちのインストールでは使えないとは言いませんでした。バージョンを確認しなかったからで、バージョンの確認は自分でする必要があります。contextの表では、サーバーはツール名だけで表示され、Claudeが手を伸ばすまでは必要なときに読み込まれます。
4つのファイルと、1枚の選び方カード
4つの仕事のあと、git statusにはちょうど4つのファイルが表示されます。hookには専用のスクリプトが要らないからです。
.claude/skills/release/SKILL.md.claude/settings.json.claude/agents/conventions-auditor.md.mcp.json
これらをコミットすれば、このリポジトリの次のセッションにはすべてがそろい、クローンした人にも同じ状態が渡ります。
それぞれの選択が正しかった理由は、docsに載っている表にあります。判断用のカードとして手元に置いておけます。
| 状況 | Extension |
|---|---|
| またしても貼り付けている手順 | Skill |
| 聞かれることなく毎回必ず起きるべきこと | Hook |
| もう読まない出力で会話をあふれさせる脇の作業 | Subagent |
| Claudeが見えない場所から何度もコピーしているデータ | MCPサーバー |
これらは組み合わせることもできます。docsは、skillとMCPサーバーの組み合わせを紹介しています。サーバーが接続を提供し、skillがClaudeにそれを上手に使う方法を教えます。skillは、フォークしたsubagentの中で動かすこともでき、その場合は読み込みがウィンドウの外に留まります。別のリポジトリでも同じ構成が必要になったら、pluginがそれらをまとめて届ける箱になります。
カードが決めるのは種類であって、品質ではありません。ログを残すだけのhook、名前を呼ばないsubagent、モデルがすでに知っているdocsのためのサーバーは、どれも正しい種類に置かれていながら失敗しました。カードから種類を選び、そのうえで、ファイルが存在するだけでなく答えを返すようにしてください。
結論: どれをいつ使うか、使わない場面、1つのルール
打ち直していた4つの仕事は、すべてまず間違ったやり方で試され、画面上で失敗し、いまはリポジトリの中で動くファイルになっています。リリースは、打つだけのコマンドです。データのルールは、編集を拒否してClaudeに確認させるhookです。監査は、要約として返ってきます。docsの質問には、Claudeが自力では見つけられなかった設定とバージョンの答えが返ります。
最初に紹介した、実行されたのに書き込みを通してしまったhookは、これらすべての土台にあるルールを教えてくれました。hookはコマンドが返すものだけで判断するので、ログを残すだけのhookは、別の服を着たお願いにすぎません。
これらのどれも要らない場合もあります。
- Claudeが何度も間違える規約は、手順ではなく1行として、これまでどおりメモリファイルに置きます。
- 行き来しながら進める必要があり、自分の履歴が見えている状態が要る仕事は、subagentではなくメインのスレッドに残します。
- モデルがすでに知っているライブラリのdocsサーバーは、contextを使うだけで何も変えません。モデルが持てないものをつなぎ、すでに持っているものはつながないでください。
覚えておくルールは、打ち直す仕事ごとに専用のファイルを用意し、カードから種類を選び、その中で確認なしに動くのはhookだけだと知っておくことです。
AIDive