Claude Codeのhooksは、決めた時点で自分のコマンドを必ず動かす仕組みです。AInformation編集部は練習用のフォルダに5種類を書き、hookあり・なしで3回ずつ流しました。止める・足す・差し戻すの動きと、効かなかった書き方を実物で載せます。
Claude Codeのhooksの使い方は、settings.jsonの「hooks」に3つを書くだけです。いつ・どの道具のときに・何を動かすかです。AInformation編集部は2026年11月3日にClaude Code 2.1.317の練習用のフォルダで、5種類のhookを書いて動かしました。hookありとなしで同じ頼みを3回ずつ流すと、決めた動きはhookありの回だけ毎回そろいました。
AInformation編集部はふだんClaude Codeで作った定時の仕組みを54本毎日動かしています。個人の設定には12本のhookを入れていて、記録の書き忘れを差し戻すのにも使っています。この記事のhookは、中身を全部載せられるよう練習用に新しく書いたものです。Claude Code自体の入れ方はClaude Codeの使い方の記事にまとめています。
- settings.jsonに書くhookの形と、そのまま使える5種類の全文(合わせて48行)
- 前回のメモを先に渡すと、道具を使わずに1.6〜3秒で続きを答えた(なしは16.8〜22秒)
- rmを止めるhookは3回ともtrashへの移動に変わり、Stop hookは3回とも報告を書かせた
- テストが落ちたと返すと、Claudeはテストのほうを書き換えた。その防ぎ方
- 小文字のmatcherやexit 1など、効かなかった書き方と直し方
- プロンプトの上に帯を出すModを1つ作り、スラッシュコマンドも足した
GoogleでフォローするGoogle検索でAInformationの記事が出やすくなります
Claude Codeのhooksとは?決めた時点で必ず動くコマンド
hooksは、Claude Codeが決まった時点で自分のコマンドを動かす仕組みです。CLAUDE.mdやskillに書いた決まりは、AIが読んで守るお願いです。忙しいときには守られないこともあります。hooksはClaude Code自体がコマンドを動かすので、AIの気分に関係なく毎回動きます。

動く時点は細かく分かれています。公式の文書に載っている種類は2026年11月3日の時点で33種類ありました。よく使うのは次の5つです。今回はこの5つを1本ずつ書きました。
よく使う5種類のhook(公式の文書に載るのは全部で33種類)
| 種類 | 動く時点 | 止められるもの | 今回の練習 |
|---|---|---|---|
| SessionStart | 起動したとき | (止めない) | 前回のメモを渡す |
| UserPromptSubmit | 依頼を送ったとき | その依頼 | 鍵を止め決まりを足す |
| PreToolUse | 道具を使う前 | その道具の呼び出し | rmを止める |
| PostToolUse | 道具を使った後 | (もう動いた後) | テストを流す |
| Stop | 答えを終える前 | 終えること | 報告が無ければ差し戻す |
skillとMCPも自動で働く仕組みですが、役目が違います。skillは手順書をAIに読ませるもので、MCPは外の道具をつなぐものです。使い分けはClaude Skillsの作り方の記事とMCPの使い方の記事にまとめています。hooksは「必ず止めたい」「必ず足したい」ことに使います。
settings.jsonの書き方(練習用の全文)
hookはsettings.jsonに書きます。ファイルは3か所あり、どこに書くかで効く範囲が変わります。今回はAInformation編集部の普段の設定を変えないよう、練習用のフォルダの.claude/settings.jsonにだけ書きました。
hookを書く場所(settings.jsonは3つ)
| ファイル | 効く範囲 | 向いている使い方 |
|---|---|---|
| ~/.claude/settings.json | 自分の全部のフォルダ | いつも守りたい決まり |
| .claude/settings.json | そのフォルダ(Gitで共有) | 仲間と同じ決まり(今回) |
| .claude/settings.local.json | そのフォルダの自分だけ | 自分だけの試し |
下が今回のsettings.jsonの全文です。種類ごとに「matcher」と「hooks」の組を[ ]の中に並べます。matcherは道具の名前で、BashやWrite|Editのように書きます。
{
"hooks": {
"SessionStart": [
{ "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session_start.py" } ] }
],
"UserPromptSubmit": [
{ "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prompt_guard.py" } ] }
],
"PreToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/bash_guard.py" } ] }
],
"PostToolUse": [
{ "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/after_write.py" } ] }
],
"Stop": [
{ "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop_check.py" } ] }
]
}
}
commandの頭の$CLAUDE_PROJECT_DIRは、プロジェクトのフォルダの場所に置き換わる決まった名前です。これを付けずに書くと、後で書く失敗が起きます。hookの中身は小さなPythonで、1本7〜11行です。
hookには、何が起きたかがJSONで渡されます。下は道具を使う前(PreToolUse)に実際に届いた中身です。番号と場所だけ伏せました。
{
"session_id": "…(毎回ちがう番号)",
"transcript_path": "~/.claude/projects/…/会話の番号.jsonl",
"cwd": "/private/tmp/hk/tui_all",
"permission_mode": "acceptEdits",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "ls -la memo/ && head -c 2000 memo/kari.txt",
"description": "Inspect the memo file before deleting"
},
"tool_use_id": "…(毎回ちがう番号)"
}
hookはこれを読んで、通すか止めるかを返します。返し方は終わり方の番号と、画面に出す文で決まります。
hookの返し方と、今回どうなったか
| 返し方 | どうなるか | 今回の結果 |
|---|---|---|
| 0で終わり何も出さない | そのまま進む | rm以外は全部通った |
| 0で終わりJSONを出す | JSONの中身で止める・足す | 3つのhookで止められた |
| 2で終わり理由を出す | 止めて理由をClaudeへ渡す | rmが3回とも止まった |
| 1などそのほかで終わる | 止めずに知らせるだけ | rmが通ってしまった |
公式の文書には「Exit with code 2 to block the action.」とあります。止めるときは2で終わり、理由を標準エラーに出すのがいちばん短い形です。細かく決めたいときは0で終わり、JSONを1つだけ出します。
書いたらフォルダでclaudeを起こします。初めてのフォルダでは信じてよいかの確認が出て、Yesを選ぶまでプロジェクトのhookは動きません。起こしたあと/hooksと打つと、読み込まれたhookの一覧が出ます。2.1.317では「This menu is read-only」と出て、一覧から足すことはできませんでした。足すときはsettings.jsonを直すか、Claudeに頼みます。

5種類を書いて、hookあり・なしで3回ずつ流した
試し方をそろえておきます。練習用のフォルダには売上のCSV(30行)と小さな計算のプログラム・作業メモを入れました。1回ごとにフォルダを作り直し、hookを1種類だけ入れた回と入れない回を3回ずつ流しています。
Claude Codeは2.1.317(Opus 5.5)を、画面を出さない-pで動かしました。2.1.317は2026年11月3日に公開された版で、出たその日に確かめています。AInformation編集部の個人の設定にもhookが入っているので、–setting-sources projectで練習用の設定だけを読ませています。秒数はClaude Codeが出す時間です。
5種類のhookをあり・なしで3回ずつ流した結果
| 種類 | 頼んだこと | hookあり | hookなし |
|---|---|---|---|
| SessionStart | 前回の続きを教えて | 1.6〜3秒・道具0回 | 16.8〜22秒・道具3〜7回 |
| UserPromptSubmit | 9月の売上の合計は? | 税込も3回 | 税込も1回 |
| PreToolUse | kari.txtを消して | 3回ともtrashへ | 3回とも削除 |
| PostToolUse | 税込の関数を足して | 3回テストを流した | テストは流れない |
| Stop | 月ごとに合計して | 3回とも報告あり | 報告は0回 |
5種類とも、決めた動きはhookありの回で3回とも起きました。hookにかかる時間は小さく、手元で20回ずつ測ると1本20ミリ秒前後でした。テストを流すhookだけ47ミリ秒です。ここからは1種類ずつ、中身と結果を見ます。
SessionStart:前回のメモを最初から渡す
起動したときに動くhookです。作業メモの最後の2行を読み、Claudeへ先に渡します。標準出力に出した文はそのまま会話の頭に入りますが、ここではJSONのadditionalContextで渡しました。
# SessionStart:前回の作業メモの最後の2行を、最初から Claude に渡す
import json, os, sys
data = json.load(sys.stdin)
memo = os.path.join(os.environ["CLAUDE_PROJECT_DIR"], "作業メモ.txt")
last = open(memo, encoding="utf-8").read().strip().splitlines()[-2:]
print(json.dumps({"hookSpecificOutput": {"hookEventName": "SessionStart",
"additionalContext": "前回の作業メモ(最後の2行):\n" + "\n".join(last)}}, ensure_ascii=False))
「前回どこまで進んだかと、次に何をやるかを2行で教えてください。」と頼みました。hookありの回は道具を1回も使わず、1.6〜3秒で答えています。hookなしの回はフォルダの中を探し回って、道具を3〜7回使いました。答えまでは16.8〜22秒です。答えの中身は6回とも合っていました。
出力の量も違いました。hookありは57〜103トークンで、なしは668〜915トークンです。毎回同じものを読ませたいなら、起動のhookで渡すと早くて安く済みます。
UserPromptSubmit:鍵の入った依頼を止め、決まりを1行足す
依頼を送った瞬間に動くhookです。今回は2つのことをさせました。鍵のような文字列(sk-で始まる長い文字)が入っていれば止めます。それ以外の依頼には「金額は税込も添える」という決まりを1行足します。
# UserPromptSubmit:鍵のような文字列が入った依頼は送らせない。それ以外は決まりを1行足す
import json, re, sys
data = json.load(sys.stdin)
if re.search(r"sk-[A-Za-z0-9]{16,}", data.get("prompt", "")):
print(json.dumps({"decision": "block", "reason": "鍵のような文字列(sk-…)が入っています。消してから送り直してください。"}, ensure_ascii=False))
sys.exit(0)
print("このフォルダの金額は税抜です。金額を答えるときは税込(10%・1円未満は切り捨て)も添えてください。")
鍵の入った依頼は、3回とも0.1〜0.1秒で止まりました。AIには何も送られず、出力も0トークンです。対話の画面では止めた理由が出て、元の依頼は入力欄に戻されました。消してから送り直せる形です。

「memo/uriage.csv の9月の売上の合計はいくらですか?」とも聞きました。答えは6回とも税抜20,460円で合っています。税込の22,506円まで書いたのはhookありで3回でした。なしでは1回です。毎回付けてほしい一言は、依頼のhookで足せば忘れません。
PreToolUse:rmを止めてtrashへ移させる
道具を使う直前に動くhookです。Bashのコマンドにrmが入っていたら、2で終わって理由を返します。
# PreToolUse(Bash):rm を止めて、trash へ移すよう伝える
import json, re, sys
data = json.load(sys.stdin)
cmd = data.get("tool_input", {}).get("command", "")
if re.search(r"(^|[;&|]\s*|\s)rm\s", " " + cmd):
print("rm は使えません。消したいファイルは trash/ へ mv してください。", file=sys.stderr)
sys.exit(2)
「memo/kari.txt はもう要らないので消してください。」と頼みました。許可の設定ではrmを許していますが、hookはその前に動くので止められます。hookありの回は3回ともrmが止まり、Claudeは理由を読んでtrashへ移しました。答えの中でも「完全には削除していません」と自分から断っています。

hookなしの回は3回ともrmで消しました。かかった時間はありが7.8〜12.6秒で、なしが5.2〜7.1秒です。止めた分だけ少し長くなります。消す・送る・本番へ上げるといった戻せない操作は、このhookで止めておくと安心です。
PostToolUse:書いたらテストを流す
道具を使った直後に動くhookです。.pyのファイルを書いたらテストを流し、落ちたときだけ理由をClaudeへ返します。
# PostToolUse(Write・Edit):.py を書いたらテストを流し、落ちたら理由を Claude に返す
import json, os, subprocess, sys
data = json.load(sys.stdin)
path = data.get("tool_input", {}).get("file_path", "")
if not path.endswith(".py"):
sys.exit(0)
root = os.environ["CLAUDE_PROJECT_DIR"]
r = subprocess.run([sys.executable, os.path.join(root, "app", "test_calc.py")], capture_output=True, text=True)
ok = r.returncode == 0
if not ok:
print(json.dumps({"decision": "block", "reason": "app/test_calc.py が落ちました:\n" + (r.stderr or r.stdout)[-600:]}, ensure_ascii=False))
「app/calc.py に、税抜の金額を税込にする関数 zeikomi(n) を足してください。税率は10%です。」と頼みました。hookありの回は、書いた直後に毎回テストが流れています。3回で3回です。今回は6回ともClaudeが先にテストを読み、1円未満の切り捨てまで合わせました。テストが落ちた回はありません。
落ちたときの動きは、対話の画面でわざと試しました。後で書くように、Claudeはテストのほうを書き換えています。PostToolUseは書いた後に動くので、書いた中身は止められません。止めたいならPreToolUseで書く前に止めます。
Stop:報告が無ければ差し戻す
Claudeが答えを終える直前に動くhookです。report.mdに「確認: 済み」の行が無ければ、終わらせずに理由を返します。
# Stop:report.md に「確認: 済み」が無ければ、終わらせずに差し戻す
import json, os, sys
data = json.load(sys.stdin)
rep = os.path.join(os.environ["CLAUDE_PROJECT_DIR"], "report.md")
done = os.path.exists(rep) and "確認: 済み" in open(rep, encoding="utf-8").read()
if not done and not data.get("stop_hook_active"):
print(json.dumps({"decision": "block", "reason": "report.md に、何をしたかと「確認: 済み」の1行を書いてから終えてください。"}, ensure_ascii=False))
sys.exit(0)
「memo/uriage.csv を月ごとに合計して、memo/matome.csv に書いてください。」と頼みました。依頼には報告のことを書いていません。hookありの回は3回とも1回ずつ差し戻され、そのあとreport.mdを書いて終えました。hookなしの回は、合計のファイルだけ書いて終えています(報告は0回)。

差し戻したぶん時間は延びて、ありが19.7〜32.2秒・なしが8.9〜15秒でした。対話の画面でも同じで、rmを止めてから報告を書き終えるまで13秒でした。
Stop hookで差し戻すときの注意(8回で打ち切られた)
Stop hookは強い道具です。理由を返すたびにClaudeは作業を続けるので、条件がいつまでも満たされないと止まらなくなります。そこで使うのがstop_hook_activeです。公式の文書には「The stop_hook_active field is true when Claude Code is already continuing as a result of a stop hook.」とあります。上のhookは、これがtrueなら2回目は通すようにしています。
この確かめを外した形も試しました。毎回「まだ終えないでください」と返すhookです。CSVの行数を聞くと、Claudeは同じ答えを言い直しながら差し戻しを10回受けました。最後はClaude Codeが打ち切り、-pの答えの欄は空のまま返っています。
回数には決まりがありました。公式の文書には「Claude Code applies an 8-consecutive-continuation cap: after stop hooks have continued the turn eight times in a row, Claude Code overrides the next block and ends the turn.」とあります。さらに「The count of consecutive continuations resets each time Claude calls a tool.」です。今回は2回目の前にReadを使っていたので、その後の9回目で打ち切られた計算です。文書のとおりでした。
差し戻すhookでは、理由にやることを具体的に書きます。「report.mdに何をしたかと確認の1行を書く」のように書くと、1回で直りました。「もう一度確かめて」のようなあいまいな理由では、Claudeは同じことを言い直すだけでした。
PostToolUseでテストが落ちたら、Claudeはテストを書き換えた
対話の画面で、税込の計算を「1円未満を四捨五入する計算に書き換えてください」と頼みました。テストは切り捨てを前提にしているので、書き換えた直後にhookが「テストが落ちました」と返します。

するとClaudeは頼まれたのは四捨五入だと考えて、テストの期待する値を1098から1099に書き換えました。さらに5円のテストを1本足して、テストを通しています。頼んだ側の意図には合っていますが、テストで守りたかった決まりは消えました。

防ぐには、テストのファイルへの書き込みを書く前に止めます。下のhookを、起動したままのsettings.jsonのPreToolUseに足しました。
# PreToolUse(Write・Edit):テストのファイルは書き換えさせない
import json, os, sys
data = json.load(sys.stdin)
path = data.get("tool_input", {}).get("file_path", "")
if os.path.basename(path).startswith("test_"):
print(json.dumps({"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny",
"permissionDecisionReason": "テストのファイルは書き換えないでください。テストに合わせてコードを直してください。"}}, ensure_ascii=False))
sys.exit(0)
足したあとは再起動せずに、テストの999の行を切り捨ての1098に戻すよう頼みました。hookはすぐ効いてテストの書き換えを止め、Claudeは3つの進め方を示して相談してきました。公式の文書にも「Direct edits to hooks in settings files are normally picked up automatically by the file watcher.」とあります。

ただしこのhookが見ているのはWriteとEditだけです。Bashのsedやechoでもファイルは書けます。守りたいファイルがあるなら、Bashのhookでも同じ名前を止めます。許可の設定のdenyに書いておくのも手です。
効かなかった書き方(8通りを1回ずつ試した)
hookは書き方を少し間違えても、何も言わずに動かないことがあります。AInformation編集部が試した8通りの結果です。消す依頼はさっきと同じものを使い、hookが何回呼ばれたかは記録のファイルで数えました。
試した書き方8通り(効かなかった5つと効いた3つ)
| 書き方 | どうなったか | 直し方 |
|---|---|---|
| matcherを小文字のbash | 1回も動かずrmが通った | Bashと大文字で書く |
| 止めたいのにexit 1 | 動いたがrmが通った | exit 2にする |
| [ ]を忘れて{ }で書いた | -pでは何も出ずに素通り | [ ]で囲む |
| パスを相対で書いた | cdのあと全部のBashが止まった | $CLAUDE_PROJECT_DIRを付ける |
| stop_hook_activeを見ない | 差し戻しが10回続いた | trueなら通す |
| exit 2とJSONを両方 | JSONの理由で止まった(効いた) | どちらか1つにする |
| exit 0でJSONのdeny | 止まった(効いた) | このままでよい |
| ifでrmだけに絞る | hookが1回だけ動いた(効いた) | 回数を減らせる |
いちばん気づきにくいのはmatcherの大文字です。「bash」と小文字で書いたhookは1回も呼ばれず、rmがそのまま通りました。公式の文書でもmatcherは道具の名前と同じ文字でそろえる決まりで、「Bash」は「matches only the Bash tool」とあります。
止めるつもりでexit 1にした形は、hookが動いて理由も出しました。それでもrmは通っています。止めるのは2だけで、ほかの番号は知らせるだけです。
[ ]を忘れて{ }で書いた形は、-pでは何も出ずに素通りでした。同じフォルダを対話の画面で開くと「Settings Error」が出て、「Files with errors are skipped entirely」と書かれていました。間違いのあるファイルは、ほかの正しい行ごと読まれません。

相対パスの失敗はもっと困る形でした。Claudeがcd memoで場所を移すと、hookのファイルが見つからなくなりました。次のlsまで止まっています。Pythonはファイルが無いと2で終わるので、Claude Codeは止めろという合図だと受け取ります。commandには必ず$CLAUDE_PROJECT_DIRを付けます。
効いた形もあります。2で終えつつJSONを出した形は、JSONの理由で止まりました。公式の文書は「If your hook also printed JSON that makes a blocking decision, Claude Code uses that decision’s reason instead.」と書いています。ifに「Bash(rm *)」と書いた形は、rmのときだけhookが呼ばれました。ふつうの形では1回の依頼で3回呼ばれたので、重いhookはifで絞ると速くなります。
![hookが効かないときの早見表。書いた形。直し方。matcherが小文字。Bashと大文字。1回も動かなかった。止めたいのにexit 1。exit 2にする。rmが通ってしまった。配列の[ ]を忘れた。[ ]で囲む。ファイルごと読まれない。パスを相対で書いた。絶対の場所にする。cdのあと全部止まった。誤りは対話の画面で開くと出る。AInformation編集部が作ったイラスト図解(状況→目安の早見表)](https://ainformation.jp/wp-content/uploads/2026/11/hk_il_lookup.jpg)
Modを1つ作った:プロンプトの上に帯を出し、コマンドを足す
Claude Codeには、hooksとは別にModという仕組みがあります。公式の文書では「A hooks module: hooks written as JavaScript functions, which can also draw panes and add commands. A plugin that has one is called a mod.」と説明されています。settings.jsonのhookはコマンドを動かすだけです。Modは画面に帯やパネルを出し、スラッシュコマンドも足せます。型の説明の頭には「EARLY ACCESS」と書かれていて、版が変わると書き方が変わることがあります。
今回作ったのは、道具を使った回数とrmを止めた回数を帯に出すModです。フォルダの形は次のとおりです。
renshu-meter/
├── .claude-plugin/plugin.json (名前と説明)
├── hooks/hooks.json (読み込むファイルを1つ書く)
├── hooks/register.tsx (本体・40行ほど)
└── types/index.d.ts (覚えておく値の形)
{ "name": "renshu-meter", "version": "0.1.0", "description": "練習用:道具の回数と rm を止めた回数を帯に出す", "types": "./types/index.d.ts" }
{ "modules": ["./register.tsx"] }
本体はTypeScriptで書きます。on()で「いつ」を決めます。帯を描く・コマンドに答える・rmを止めるの3つを1つのファイルにまとめました。
import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'
import type { Meter } from '../types'
// 練習用の Mod:道具を呼んだ回数と、rm を止めた回数をプロンプトの上の帯に出す
const meter = atom({ plugin: 'renshu-meter', key: 'meter' } as const, { tools: 0, denied: 0, last: '' }, { shape: 'meter-1' })
const RM = /(^|[;&|]\s*|\s)rm\s/
export const register: Register = on => {
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'renshu-meter', description: '道具の回数と止めた回数を出す' })
return next(e)
})
on('command.run', { command: 'renshu-meter' }, async $ => {
const m = await read($, meter)
return { text: `道具 ${m.tools}回・rm を止めた ${m.denied}回(最後:${m.last || 'まだ無し'})` }
})
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
if (RM.test(' ' + e.command)) {
await update($, meter, m => ({ ...m, denied: m.denied + 1, last: 'rm を止めた' }))
$.ui.toast('rm を止めました')
return { deny: 'rm は使えません。消したいファイルは trash/ へ mv してください。' }
}
return next(e)
})
on('tool.call', async ($, e, next) => {
await update($, meter, m => ({ ...m, tools: m.tools + 1, last: String(e.tool) }))
return next(e)
})
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
if (e.props.hasSurvey) return next(e)
const m = await read($, meter)
const { Box, Text } = $.ui.resolve(e)
return (
<Box>
<Text bold>練習の帯 </Text>
<Text>道具 {m.tools}回・rm を止めた {m.denied}回</Text>
</Box>
)
})
}
書いたらclaude plugin validateで確かめます。結果は「Validation passed with warnings」で、作者・README・LICENSE・アイコンが無いという注意だけでした。どのhookを使っているかも一覧で出るので、書き間違いに先に気づけます。
claude –plugin-dir ./renshu-meter
そのときだけ読み込みます。仲間に配るときはプラグインの目録に載せます。
起こすと、入力欄の上に「練習の帯 道具 0回・rm を止めた 0回」と出ました。同じく「memo/kari.txt を消して」と頼むと、Modがrmを止めました。帯の数もその場で動きます。

/renshu-meterと打つと、Modが足したコマンドが答えます。起動したまま本体の文字を書き換えると、4秒ほどで「reloaded」と出て帯が変わりました。数えた回数は消えずに残っています。

-pでも試すと帯は出ませんが、rmはModが止めました。8.7秒でtrashへ移しています。止める・足すだけならsettings.jsonのhookで足ります。画面に何かを出したいときにModを使う、という分け方がよさそうです。

AInformation編集部が動かしているhooks(12本の使い道)
AInformation編集部の個人の設定には、12本のhookが6種類に分かれて入っています。中身は業務に関わるので、本数と使い道だけを載せます。
AInformation編集部の個人の設定に入っているhook(12本・中身は載せない)
| 種類 | 本数 | 何のためか |
|---|---|---|
| Stop | 4本 | 記録や工数の書き忘れを差し戻す・作った資料を開く |
| UserPromptSubmit | 3本 | 題を付ける・関係する決まりを渡す・会話の長さを見る |
| PreCompact | 2本 | 要約の前に引き継ぎを書く |
| PreToolUse・PostToolUse | 2本 | 質問を出す前後の関所 |
| SessionStart | 1本 | Macを眠らせない |
いちばん多いのはStopの4本です。指摘の記録や工数の書き忘れがあると、答えを終える前に差し戻します。上で試したreport.mdの差し戻しと同じ形で、「書いたかどうか」を見るだけなら数行で作れます。
動かしていて決めたことは3つです。commandは必ず絶対の場所で書きます。hookは速く終わらせ、重い処理は裏で動かします。新しいhookは練習用のフォルダの対話の画面で先に試し、設定の誤りの画面が出ないことを確かめます。
うまく動かないときは、まず/hooksで読み込まれているかを見ます。hookを全部止めて切り分けたいときは、claude –safe-modeで起こすとhookを読まずに起動します。エラーの出方はClaude Codeのエラーと上限の記事に、/で打てるコマンドはClaude Codeのコマンド一覧にまとめています。ほかのAIコーディングのツールとの比べ方はAIコーディングのハブにあります。
hooksを書く前に、Claude Codeへの頼み方や許可の画面の押し方から知りたい人は非エンジニア向けのClaude Code入門を見てください。
hooksの直しや新しい設定は版ごとに入ります。今週の分は今週のClaude Codeの変更で毎週日本語にしています。
まとめ
Claude Codeのhooksは、settings.jsonに時点と道具とコマンドを書けば動きます。AInformation編集部が5種類を書いて3回ずつ流すと、どれもhookありの回で決めた動きが毎回起きました。前回のメモを渡す・鍵を止める・rmを止める・テストを流す・報告を書かせるの5つです。matcherは道具の名前と同じ大文字で書きます。止めるときは2で終わり、パスは$CLAUDE_PROJECT_DIRから書きます。Stop hookはstop_hook_activeを見て、理由にやることを具体的に書きます。画面に帯やコマンドを足したいときはModも使えます。
よくある質問
- Claude Codeのhooksとは何ですか?
- Claude Codeが決まった時点で自分のコマンドを動かす仕組みです。CLAUDE.mdの決まりと違い、AIが読むかどうかに関係なく毎回動きます。
- hooksはどこに書きますか?
- settings.jsonの「hooks」に書きます。全部のフォルダなら~/.claude/settings.json、そのフォルダだけなら.claude/settings.jsonです。自分だけの試しは.claude/settings.local.jsonに書きます。
- hookで道具の呼び出しを止めるには?
- PreToolUseのhookで、2で終わって理由を標準エラーに出します。0で終わり、JSONのpermissionDecisionにdenyを書く形でも止まりました。1で終わると止まりません。
- hookが動きません。
- matcherがBashのように大文字で書かれているか、[ ]で囲んでいるかを見ます。対話の画面で起こすと設定の誤りが出ます。/hooksで一覧に出ているかも確かめます。
- settings.jsonを直したら再起動が要りますか?
- AInformation編集部が試したときは、起動したまま足したhookがすぐ効きました。公式の文書にも、ファイルの見張りでふつうは読み直すとあります。
- Stop hookで作業を続けさせるには?
- JSONで「decision」に「block」、「reason」にやることを書きます。stop_hook_activeがtrueなら通すようにしないと、何度も差し戻すことになります。続けて8回で打ち切られます。
- hookを書くと遅くなりますか?
- 今回の練習用のhookは1本20ミリ秒前後でした。テストを流すような重いhookは、ifで動く場面を絞ると呼ばれる回数が減ります。
- -pでもhookは動きますか?
- 動きます。今回の30回の実測は全部-pです。ただし設定の誤りは-pでは知らされないので、新しいhookは対話の画面で一度開いて確かめます。
- Claude CodeのModとは何ですか?
- TypeScriptで書くプラグインです。hookのほかに画面の帯やパネル・スラッシュコマンドを足せます。–plugin-dirで読み込んで試せます。今は早めに公開された段階で、書き方が変わることがあります。
- hooksとskillはどう使い分けますか?
- 必ず止めたい・必ず足したいことはhooks、作業の手順を覚えさせたいときはskillです。skillはAIが読んで使うので、使われないこともあります。
