この記事は、連載「読まない技術」の第 4 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、スキルの話です。スキルというのは、AI にカスタムコマンドやツールを登録しておく仕組みのことです。序論の足すほど薄まるが、ルールだけでなくツールでも起きます。違うのは、薄めているのが使ってほしい当の仕組みだということです。スキルは、並べるほど選ばれなくなる。

まず一つだけ、数えてみてください。

AI に登録してあるスキル(カスタムコマンド、ツール)はいくつありますか。そのうち、直近 1 週間で AI が自分から選んで使ったものはいくつありますか。

登録した数より使われた数がずっと少ないなら、書き方が悪いのではありません。スキルは「こういうときに使う」という条件文付きのツールの登録で、使うかどうかの判断を AI に委ねます。コストは二重です。登録しただけで読む量が増え、そのうえ選ばれません。

似た条件文が並ぶほど食い合って、一番使ってほしいものほど埋もれます。序論の「足すほど薄まる」が、ルールだけでなくツールでも起きています。

私の手元の本番プロジェクト——YouTube の音楽動画でタイピング練習ができる Web サービス typingtube——には、AI に回させるスクリプトが 84 本あります(十数言語ぶんの翻訳の検証、デザインのラチェット、ベンチマーク、キャラクター画像の生成……画面に出ている機能の数だけ、裏方のツールがあります)。スキル登録は 1 個です。84 本ぶんの説明文はどのセッションにも常駐していません。

理解したことは一文にできます。

スキルは使う条件を並べるほど選ばれなくなる。だから選ばせるのではなく、使う前に読ませる。

CC BY 4.0

なぜ、並べるほど選ばれなくなるのか

スキルの条件文は、セッションが始まった時点で全部載っています。載るのは名前と description だけです。本文は、呼ばれたときに会話へ足されます。Claude Code のスキルのドキュメントは、その description を when_to_use と合わせて 1,536 文字で切り、一覧の全体にコンテキストの 1% という予算を置いています。予算を超えたぶんは落ちます。1 つ登録することは、この取り合いに 1 枚加えることです。

では条件文を短く薄く書けばいいのかというと、公式の指針は逆を言っています。ツール定義のドキュメントは、説明を極めて詳細に書け、それが群を抜いて最も重要だと書いています。何をするか、いつ使うか、いつ使わないか、パラメータの一つずつ、最低でも 3〜4 文。そして同じページが、関連する操作は少ないツールにまとめよとも書いています。1 つあたりは厚く、数は少なく。数を増やす側の設計は、最初から勧められていません。

並べたものは、互いを弱めます。長い入力から目的の行を探させる測定では、意味の近い紛らわしいものが 1 つ混じるだけで精度が落ちました。スキルの条件文どうしは、まさに意味の近いものの集まりです。「テストを走らせるとき」「テストが落ちたとき」「CI を直すとき」——隣り合うほど、見分けが付きません。

そのうえで、選ぶのは最後まで AI です。「こういうときに使う」と書いた時点で、いま自分が該当するかを決めるのは読む側になります。

長いセッションでは、選択肢そのものが消えます。会話がコンテキストの上限に近づくと、Claude Code は履歴を圧縮します。このとき、指示ファイルとメモリの索引はディスクから読み直されます。ところがスキルの一覧は再注入されません。長く続けたセッションの後半ほど、AI は登録したものを選べなくなります。

仕組み: 選ばせる代わりに、入口で読ませる

コマンドは素のスクリプトのまま置き、使い方はドキュメントに書きます。ここまでは普通です。置くのは hook 1 つ——間違った入り方をしたときだけ止まって、読むべき 1 枚を案内する。第 1 回のラッパー案内と同じ形です。

準備が要るコマンドには、一歩進めた形を使います。例は pre-commit hook——コミットのたびに秘密情報の混入や規約違反を落とす類の仕組みで、git の hook の設置が前提です。手順どおりに設置すると .git/hooks/pre-commit が生まれます。Claude Code の hook は、AI が git commit を打ったらこのファイルの存在だけを確かめ、無ければ手順を案内して止まります。

#!/bin/bash
# .claude/hooks/require_setup.sh — 手順を踏んだ形跡(生成物)が無ければ止める(手元のコードを短くしたもの)
command=$(cat | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))')
case "$command" in *--no-verify*|*SKIP_HOOK_CHECK=*) exit 0 ;; esac   # 明示の逃げ道は通す
printf '%s' "$command" | grep -qE '(^|[;&|[:space:]])git[[:space:]]+commit([[:space:]]|$)' || exit 0
hooks_dir=$(git rev-parse --git-path hooks 2>/dev/null || echo .git/hooks)   # worktree でも正しい場所を見る
if [ ! -x "$hooks_dir/pre-commit" ]; then
  echo "Pre-commit checks are not installed. Run the setup in docs/setup.md (bash scripts/setup_hooks.sh) first" >&2
  exit 2   # 実行させず、stderr の案内を AI に読ませる
fi
exit 0

この hook は「ドキュメントを読んだか」を判定していません。読んだかどうかは測れないからです。見ているのは「ドキュメントを見なければ作られないファイルの存在」だけ。そこにファイルがあるなら、経路はどうあれ手順は踏まれています。

逃げ道は、明示のときだけ開けます。上のコードが通しているのは SKIP_HOOK_CHECK= と、git が元から持っている素通りの口の --no-verify です。

これで回るのは、AI は止められるとドキュメントを探すからです。止まる → 案内を読む → 手順を踏む → 進む。「いつ使うか」を事前に選ばせる代わりに、「使うなら読む。読んでいなければ動けない」に変わり、必要になった瞬間に必要な 1 枚だけが読まれます。

この形にする前、私がクローンした作業ディレクトリに pre-commit hook が未設置のまま、39 コミット、別環境では 2 ヶ月ぶんが、どの hook も通らずに積もっていました。怖いのは落ちなかったことです。落ちないものには誰も気づきません。

CC BY 4.0 はここまで

読まなくなったもの

スキルの説明文群です。書くのも、AI に毎セッション読ませるのも、選ばれるように条件文をチューニングし続けるのも、私はやめました。84 本のスクリプトの使い方は各ドキュメントに 1 枚ずつあり、読まれるのは、hook などの仕組みが必要だと判断したときだけです。

CC BY 4.0

注意: 入口を全部に付けるのではない

hook を 84 本ぶん書くわけではありません。止める価値があるのは間違えると高くつく入口だけ——私の環境ではテスト実行、コミット、サブエージェント起動の数か所です。残りは間違えてもすぐ分かって安いので素のまま。全部にガードレールを立てたら、条件文の一式を hook で作り直すだけです。

検証手順: 外して、止まることを確かめる

前提

  • この回の hook を 1 つ登録し終えていること
  • 止める対象のコマンドと、存在を確かめるファイルの組が決まっていること(本文の例では、AI が git commit を打ったときに .git/hooks/pre-commit の存在を見ます)

所要時間: 5 分

手順

  1. あなたが、存在を確かめている側のファイルを退避します(mv .git/hooks/pre-commit /tmp/ のように)。消すのではなく退避です。後始末で戻します
  2. AI に、対象のコマンドを実行させます(本文の例なら「コミットして」と頼みます)

合格条件(すべて満たすこと)

  • コマンドが実行されずに止まる
  • 止まった場所に、読むべきドキュメントの在りかが出ている

合格しなかったとき

  • 止まらずに通ってしまった —— hook が登録されていないか、見ているパスが手順 1 で退避したファイルと違います
  • 止まったが、案内が出ない —— hook は半分しか働いていません。行き先を示されなかった AI は、その場で自分の回り道を探し始めます

後始末

  • 退避したファイルを元の場所へ戻し、同じコマンドをもう一度 AI に実行させます。今度は止まらずに通れば終わりです
  • まだ止まるなら、hook が見ているパスと、あなたが戻した場所が食い違っています

止める仕組みは「止まらない」方向に壊れます。一度も止まったことのない hook は、動いている hook と同じ見え方をします。どちらも、黙って通すからです。


CC BY 4.0 はここまで

次回からは応用編、「サブエージェントの出力だけは読め」。サブエージェントの出力だけは、読まないととんでもないことになります。

連載「読まない技術」