【Claude Code】Hooksの使い方をわかりやすく解説
この記事では、AI(Claude)がコードを編集する際などに、開発者が用意したプログラムを自動で実行させる「Hooks」の仕組みを解説します。コードの品質チェックや重要ファイルの保護を自動化する方法、Pythonでの活用、そして特定の操作を停止させる終了コードの役割を学べます。
開発環境
- OS: Windows10(WSL2 / Ubuntu)
- Editor: Visual Studio Code
- Node.js: v22.22.2
- claude-code: 2.1.118
Claude Code Hooks(フックス)とは?
Claude Code Hooks(フックス)とは、AIであるClaudeが特定の動作を行う「タイミング」に合わせて、開発者が用意したプログラム(スクリプト)を自動的に実行させる仕組みのことです。プログラミングにおける「フック」とは、「特定のイベントが発生したときに、あらかじめ指定した処理を割り込ませる、または実行させる」という意味で使われます。
具体的には、Claudeが外部のプログラムや機能(ツール)を使用する直前や直後、またはユーザーへの応答が完了したときなど、あらかじめ決められた瞬間に、開発者が書いたスクリプトを自動で動かすことができます。
- この仕組みを導入するには、作成したスクリプトを
.claude/hooks/という特定のフォルダに置き、.claude/settings.jsonという設定ファイルに簡単な内容を記述するだけで動作します。これにより、特別な設定を多くすることなく、すぐに利用を開始できます。 - フックスを活用することで、さまざまな自動処理を実現できます。例えば、「ソースコードファイルを編集した際に、自動的にコードの品質チェック(lint)を行う」ことができます。これにより、コードの誤りやスタイル違反を早期に発見し、修正する手間を省けます。また、「.envファイル(環境変数など、機密情報を含む重要な設定ファイル)に触ろうとした際に、誤操作を防ぐためにその操作をブロックする」といった、セキュリティや安全性を高める制御も可能です。
- フックスで実行するスクリプトは、シェルスクリプト、Python、Node.jsなど、さまざまなプログラミング言語で書くことができます。そのため、開発者が使い慣れている言語で柔軟にスクリプトを作成できる点が大きな利点です。
公式ドキュメント: https://code.claude.com/docs/ja/hooks
フックが割り込むタイミング
1Claude Code 2 ↓ 3[PreToolUse hook] ← ツール実行前 4 ↓ 5ツール実行(Edit / Bash / Write など) 6 ↓ 7[PostToolUse hook] ← ツール実行後 8 ↓ 9[Stop hook] ← 応答完了時
| イベント名 | タイミング |
|---|---|
PreToolUse | Claudeがツールを実行する直前 |
PostToolUse | Claudeがツールを実行した直後 |
Stop | Claudeの応答が完了したとき |
この情報は、ClaudeというAIが何らかの「ツール」を使用する際に、どのような「フック」が割り込むかを示しています。 「フック」とは、特定のイベントが発生する前や後に、あらかじめ決められた処理を自動的に実行させるための仕組みのことです。プログラミングでは、このような機能を使ってシステムの動作を制御することがよくあります。
具体的に、Claudeがツールを使う過程で発生するフックは以下の3つです。
-
PreToolUse: このフックは、Claudeが「ツールを実行する直前」に発生します。例えば、Claudeが何らかの編集(Edit)、コマンド実行(Bash)、書き込み(Write)などのツールを使おうとするその瞬間の前です。このタイミングで、ツールを使う前の準備や、実行しても良いかの最終確認を行うような処理を挟むことができます。 -
PostToolUse: このフックは、Claudeが「ツールを実行した直後」に発生します。ツールによる処理が完了した直後です。例えば、ツールが期待通りに動作したかを確認したり、ツールが生成した結果を保存したり、次のステップに進むための準備をしたりする処理をこのタイミングで行うことができます。 -
Stop: このフックは、Claudeの「応答が完全に完了したとき」に発生します。つまり、Claudeがユーザーへの回答をすべて終え、一連の処理が終了したと判断された最終段階です。このタイミングで、一連の処理のまとめを行ったり、最終的なログを記録したり、ユーザーに完了通知を送ったりするような処理を行うことができます。
これらのフックを利用することで、Claudeのツール利用プロセスをより細かく制御し、状況に応じた柔軟なシステム連携や処理の自動化を実現することが可能になります。
ディレクトリ構成
システム開発において、ファイルやフォルダをどのように配置するかは非常に重要です。これを「ディレクトリ構成」と呼びます。適切なディレクトリ構成は、プロジェクトの管理を容易にし、複数の開発者が協力して作業を進める上で役立ちます。
1.claude/ 2├── hooks/ 3│ ├── run-linter.sh ← スクリプトをここに置く 4│ └── protect-env.sh 5└── settings.json ← hook設定を書く
ここで示されている .claude/ ディレクトリは、特定のツールやシステムが利用する設定ファイルやスクリプトをまとめて管理するための場所です。「.」(ドット)から始まるフォルダは、通常、隠しファイルや隠しフォルダとして扱われ、システム設定など、ユーザーが直接頻繁に操作しないけれど重要な情報を格納することが多いです。
.claude/hooks/ ディレクトリには、「hooks(フック)」と呼ばれるスクリプトファイルが格納されます。フックとは、特定のイベント(例えば、コードをコミットする前やデプロイする前など)が発生した際に、自動的に実行されるプログラムのことです。
run-linter.shのようなスクリプトは、ソースコードが定められたコーディング規約(リンター)に沿っているか自動的にチェックするために使用されることがあります。protect-env.shのようなスクリプトは、開発環境や本番環境に関する重要な設定情報が誤って変更されたり、公開されたりしないように保護するための処理を実行する可能性があります。
これらのスクリプトは、開発プロセスを自動化し、コードの品質を維持したり、セキュリティを強化したりするために利用されます。
.claude/settings.json ファイルは、フックスクリプトの動作や、その他のツールの設定を記述するためのファイルです。JSON形式は、データを構造化して表現するためによく使われる形式で、人間にも読みやすく、プログラムにも解析しやすいという特徴があります。例えば、どのフックスクリプトをいつ実行するか、どのような条件で実行するかといった詳細な設定がこのファイルに書かれます。
実践①:PostToolUse でlint自動実行
この記事では、AIコーディングアシスタントであるClaudeがコードを編集した後に、自動で「lint」(コードの品質チェック)を実行する方法を学びます。これにより、Claudeが生成したり修正したりしたコードが、決められたルールに沿っているか、間違いがないかをすぐに確認できるようになります。
「lint」とは、プログラムの文法的な誤りや、プログラジェクトで定めたコーディング規約(書き方のルール)からの逸脱を自動的に検出してくれるツールのことです。ESLintは、特にJavaScriptやTypeScriptのコードをチェックするための、非常に人気のあるlintツールです。
「PostToolUse」は、Claudeが何らかのツール(例えばファイルを書き換えたり、コマンドを実行したりするツール)を使った後に、特定の処理を自動で実行させるための機能です。
スクリプトを作成する(lint実行用)
まず、Claudeが編集したファイルをチェックするためのスクリプトを作成します。
1touch .claude/hooks/run-linter.sh 2chmod +x .claude/hooks/run-linter.sh
これらのコマンドは次のことを行います。
touch .claude/hooks/run-linter.sh:.claude/hooks/ディレクトリの中に、run-linter.shという名前の空のファイルを作成します。このファイルがlintを実行するためのスクリプト本体となります。chmod +x .claude/hooks/run-linter.sh: 作成したrun-linter.shファイルに「実行権限」を与えます。これにより、このファイルをコマンドとして実行できるようになります。
次に、作成したrun-linter.shファイルに以下の内容を記述します。
1# .claude/hooks/run-linter.sh 2#!/bin/bash 3 4# ClaudeからJSON形式でツール情報が標準入力で渡される 5INPUT=$(cat) 6 7# 編集されたファイルのパスを取得 8FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') 9 10# TypeScript / JavaScript ファイルのみ対象 11if [[ "$FILE_PATH" == *.ts ]] || [[ "$FILE_PATH" == *.js ]]; then 12 LINT_OUTPUT=$(npx eslint "$FILE_PATH" 2>&1) 13 if [ $? -ne 0 ]; then 14 echo "Lint errors found: $LINT_OUTPUT" >&2 15 exit 2 16 fi 17fi 18 19exit 0
このスクリプトは、次のような処理を実行します。
#!/bin/bash: このスクリプトがBashというシェルで実行されることを宣言しています。INPUT=$(cat): Claudeがファイルを編集した際、その情報がJSON形式でこのスクリプトに「標準入力」として渡されます。catコマンドはその標準入力を読み込み、その内容をINPUTという変数に保存しています。FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty'):jqコマンドを使って、INPUT変数に入っているJSONデータから「編集されたファイルのパス」を取り出しています。jqはJSONデータを処理するための強力なコマンドラインツールです。if [[ "$FILE_PATH" == *.ts ]] || [[ "$FILE_PATH" == *.js ]]; then ... fi: 取り出したファイルパスが.ts(TypeScript)または.js(JavaScript)で終わるファイルの場合のみ、その後の処理を実行します。これにより、画像ファイルやテキストファイルなど、lintの対象ではないファイルでは無駄な処理をしないようになります。LINT_OUTPUT=$(npx eslint "$FILE_PATH" 2>&1):npx eslint "$FILE_PATH"コマンドで、指定されたファイルをESLintでチェックします。npxはNode.jsのパッケージ(この場合はESLint)をインストールせずに実行できる便利なコマンドです。2>&1は、lintのエラーメッセージもLINT_OUTPUT変数に含めるための記述です。if [ $? -ne 0 ]; then ... fi: 直前のnpx eslintコマンドがエラーで終了したかどうかをチェックしています。もしエラーがあれば(終了コード$?が0以外の場合)、Lint errors found: ...というメッセージとエラー内容を表示し、スクリプト自体もエラーで終了します。exit 0: すべての処理が正常に完了した場合、スクリプトを正常終了させます。
注意:
jqコマンドはJSONデータを扱うために必要です。もしあなたのシステムにjqがインストールされていない場合は、sudo apt install jqコマンド(Ubuntu/Debianの場合)などでインストールしてください。
settings.json に設定する(PostToolUse)
次に、Claudeがファイルを編集した後に、先ほど作成したスクリプトを自動で実行するように設定します。これはプロジェクトのルートディレクトリにあるsettings.jsonファイルに記述します。
この設定は、次のような意味を持っています。
"hooks": 特定のイベント(フック)が発生したときに実行する処理をまとめるセクションです。"PostToolUse": Claudeが何らかのツールを使用した後で実行されるフックです。"matcher": "Write|Edit":Write(ファイルを書き込む)ツール、またはEdit(ファイルを編集する)ツールが使われたときにこのフックを実行するという条件を設定しています。|(パイプ)を使うことで複数のツールを指定できます。"type": "command": 実行するフックの種類が「コマンド」であることを指定しています。"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/run-linter.sh": 実行するコマンドの具体的な内容です。先ほど作成したrun-linter.shスクリプトのパスを指定しています。$CLAUDE_PROJECT_DIRは、Claude Codeが自動的にプロジェクトのルートディレクトリのパスに置き換えてくれる環境変数です。
1{ 2 "hooks": { 3 "PostToolUse": [ 4 { 5 "matcher": "Write|Edit", 6 "hooks": [ 7 { 8 "type": "command", 9 "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/run-linter.sh" 10 } 11 ] 12 } 13 ] 14 } 15}
この設定をすることで、Claudeがファイルを書き換えたり編集したりした後に、自動的にrun-linter.shスクリプトが実行されます。もしESLintがコードのエラーや規約違反を検出した場合、そのエラー内容がClaudeに渡されます。Claudeはそのエラー内容を理解し、自動的にコードの修正を試みるようになります。これにより、コードの品質を高いレベルで維持しながら開発を進めることができるようになります。
実践②:PreToolUse で.envファイルを保護
このセクションでは、大規模言語モデル(LLM)であるClaudeが、プロジェクトの大切な設定ファイルである.envファイルを編集しようとしたときに、それを自動的にブロックする仕組みについて学びます。.envファイルには、データベースのパスワードやAPIキーなど、外部に漏れてはいけない重要な情報が書かれているため、保護が必要になります。
スクリプトを作成する(.env保護用)
まず、.envファイルの編集をブロックするためのスクリプトを作成します。
1touch .claude/hooks/protect-env.sh 2chmod +x .claude/hooks/protect-env.sh
このコマンドは、以下のことを行います。
touch .claude/hooks/protect-env.sh:.claude/hooks/ディレクトリの中にprotect-env.shという名前の空のファイルを作成します。このファイルがブロック処理を行うスクリプト本体となります。chmod +x .claude/hooks/protect-env.sh: 作成したスクリプトファイルに「実行権限」を付与します。これにより、Claudeがこのスクリプトを実行できるようになります。
次に、作成したprotect-env.shファイルに以下の内容を書き込みます。
1# .claude/hooks/protect-env.sh 2#!/bin/bash 3 4INPUT=$(cat) 5 6FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') 7FILE_NAME=$(basename "$FILE_PATH") 8 9if [[ "$FILE_NAME" == ".env" ]]; then 10 echo ".env ファイルは編集できません。" >&2 11 exit 2 12fi 13 14exit 0
このスクリプトの各行について説明します。
#!/bin/bash: これはシェルスクリプトの最初の行に書く宣言で、このスクリプトが「Bash」という種類のシェル(コマンドを実行するためのプログラム)で動くことを示しています。INPUT=$(cat): この行は、Claudeがツールを使おうとするときに渡される情報(JSON形式のデータ)をINPUTという変数に格納しています。catコマンドは標準入力からデータを受け取る役割をします。FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty'):echo "$INPUT":INPUT変数に格納されたJSONデータを表示します。|: これはパイプという記号で、左側のコマンドの出力を右側のコマンドの入力として渡します。jq -r '.tool_input.file_path // empty':jqはJSONデータを処理するための強力なツールです。ここでは、入力されたJSONデータの中からtool_inputの中にあるfile_pathという項目を取り出しています。// emptyは、もしfile_pathが見つからなかった場合に空の文字列を返すようにしています。これにより、編集対象のファイルのパスを取得しています。
FILE_NAME=$(basename "$FILE_PATH"): 取得したファイルのパス(例:/path/to/.env)から、ファイル名だけ(例:.env)を取り出してFILE_NAMEという変数に格納しています。basenameコマンドはパスからファイル名を取り出す役割です。if [[ "$FILE_NAME" == ".env" ]]; then ... fi: これは条件分岐の文です。もしFILE_NAMEが「.env」と完全に一致した場合に、thenからfiの間の処理を実行します。echo ".env ファイルは編集できません。" >&2: 条件が真(ファイル名が.envだった)の場合、このメッセージを表示します。>&2は、このメッセージを「標準エラー出力」という特別な場所に出力することを意味します。エラーメッセージは通常、標準エラー出力に送られます。exit 2: スクリプトを終了させます。ここで2という終了コードを返すことが非常に重要です。この2というコードは、Claudeがツールの実行を「ブロックする」ための特別な意味を持ちます。
exit 0: もしファイル名が.envでなかった場合(if文の条件が偽だった場合)、この行が実行されます。0という終了コードは、「スクリプトが正常に終了しました」という意味です。
settings.json に設定する(PreToolUse)
次に、Claudeの設定ファイルであるsettings.jsonに、作成したスクリプトを実行する設定を追加します。
1{ 2 "hooks": { 3 "PostToolUse": [ 4 { 5 "matcher": "Write|Edit", 6 "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/run-linter.sh" }] 7 } 8 ], 9 "PreToolUse": [ 10 { 11 "matcher": "Write|Edit", 12 "hooks": [{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-env.sh" }] 13 } 14 ] 15 } 16}
この設定ファイルについて説明します。
"hooks": これは、Claudeが特定のイベント(ツールの使用など)が発生したときに、自動で何か処理を実行するための設定をまとめる部分です。"PostToolUse": これは、Claudeがツールを「使用した後」に実行されるフックの設定です。上記の例では、run-linter.shというスクリプトが実行されるように設定されていますが、今回の主題とは直接関係ありません。"PreToolUse": これが今回のポイントです。これは、Claudeがツールを「使用する前」に実行されるフックの設定です。ツールが実際に実行される前にスクリプトを動かすため、ファイルの編集をブロックするのに適しています。"matcher": "Write|Edit": この設定は、どのような操作に対してフックを実行するかを定義しています。ここでは、「Write(書き込み)」または「Edit(編集)」という操作が行われる場合にフックを有効にするという意味です。"hooks": [...]: マッチャーに一致した場合に実行される具体的なフックのリストです。"type": "command": 実行するフックの種類が「コマンド(シェルスクリプトなど)」であることを示します。"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-env.sh": 実際に実行するコマンド(スクリプト)のパスを指定しています。$CLAUDE_PROJECT_DIRは、現在のプロジェクトのルートディレクトリを示す特別な変数です。
これらの設定により、Claudeが「Write」または「Edit」という操作を実行しようとしたとき、事前にprotect-env.shスクリプトが実行されます。このスクリプトが.envファイルを検出してexit 2を返した場合、Claudeの編集操作はブロックされます。
この設定が完了すると、「.envのAPI_KEYを更新してください」とClaudeに依頼した場合、Claudeは編集をブロックされたことをユーザーに伝えてくれます。
注意:
exit 2にしないとブロックされません。exit 1はエラー扱いですが、Claudeの処理自体は続行されてしまいます。exit 2は、特定のツール操作を完全に停止させるための特別な終了コードであることを覚えておきましょう。
コラム:Pythonでも書ける
こちらのコラムでは、Pythonスクリプトをシステムの「フック」として利用する方法について解説いたします。フックとは、システムが特定の操作を行う前や後に、自動的に別の処理を差し込む仕組みのことです。
command に python3 を指定するだけで、Pythonスクリプトをhookとして使えます。
まず、Pythonスクリプトの具体的な例を見てみましょう。このスクリプトは、特定のファイルを保護するために使われます。
1# .claude/hooks/protect-env.py 2import sys 3import json 4 5input_data = json.load(sys.stdin) 6file_path = input_data.get("tool_input", {}).get("file_path", "") 7 8if file_path.endswith(".env"): 9 print(".env ファイルは編集できません。", file=sys.stderr) 10 sys.exit(2) 11 12sys.exit(0)
このPythonスクリプトは、次のような動作をします。
import sysとimport json: Pythonがシステムに関する機能(sys)とJSONデータを扱う機能(json)を使えるようにするための宣言です。input_data = json.load(sys.stdin): このスクリプトが実行されると、システムから「標準入力(sys.stdin)」を通じて、今から行われる操作に関する情報がJSON形式で渡されます。この行でそのJSONデータを読み込み、Pythonで扱えるデータ(input_data)に変換しています。file_path = input_data.get("tool_input", {}).get("file_path", ""): 受け取った情報の中から、操作対象となるファイルのパス(file_path)を取り出しています。if file_path.endswith(".env"):: 取り出したファイルのパスが.envという拡張子で終わっているかどうかをチェックしています。これは.envファイル、つまり環境変数を記述する重要な設定ファイルを意味します。print(".env ファイルは編集できません。", file=sys.stderr): もし.envファイルであれば、エラーメッセージ「.env ファイルは編集できません。」を標準エラー出力(sys.stderr)に表示します。sys.exit(2): プログラムを終了し、2という終了コードをシステムに返します。終了コード2は「エラーが発生したため、処理を中断してください」という意味になります。sys.exit(0):.envファイルでなかった場合は、プログラムを正常終了し、0という終了コードをシステムに返します。終了コード0は「問題なく処理が完了したので、そのまま処理を続けてください」という意味になります。
次に、このPythonスクリプトをフックとして利用するための設定を見てみましょう。JSON形式で記述されています。
1{ 2 "hooks": { 3 "PreToolUse": [ 4 { 5 "matcher": "Write|Edit", 6 "hooks": [{ "type": "command", "command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/protect-env.py" }] 7 } 8 ] 9 } 10}
このJSON設定は、次のような意味を持っています。
"hooks": ここにフックに関する設定を記述することを表します。"PreToolUse": これは、ツール(プログラム)が使われる「前」に実行されるフックを設定する場所です。"matcher": "Write|Edit":Write(書き込み)またはEdit(編集)といったファイル操作が行われようとするときに、このフックを適用するという条件を指定しています。"type": "command": 実行するフックの種類が、コマンドを実行するタイプであることを示します。"command": "python3 $CLAUDE_PROJECT_DIR/.claude/hooks/protect-env.py": 実際に実行されるコマンドです。python3を使って、先ほど説明した.claude/hooks/protect-env.pyというパスにあるPythonスクリプトを実行するように指示しています。$CLAUDE_PROJECT_DIRは、プロジェクトのルートディレクトリを示す環境変数です。
仕組みはシェルスクリプトと同じ。stdin からJSONを受け取り、exit 2 でブロック、exit 0 で続行します。
この仕組みは、シェルスクリプトやその他のコマンドラインプログラムと同じ原則に基づいています。フックとして実行されるPythonスクリプトは、次の流れで動作します。
stdinからJSONを受け取る: システムがフックを呼び出す際、操作しようとしているファイルの情報などをJSON形式のデータとして、Pythonスクリプトの標準入力(stdin)に渡します。exit 2でブロック: Pythonスクリプトが処理の結果としてsys.exit(2)を実行すると、システムは「異常があった」と判断し、本来行われるはずだった操作(この例では.envファイルの編集)を中断(ブロック)します。exit 0で続行: Pythonスクリプトがsys.exit(0)を実行すると、システムは「正常に完了した」と判断し、本来行われるはずだった操作をそのまま続行します。
このように、Pythonスクリプトをフックとして使うことで、システムに特定のルールやチェック機能を簡単に組み込むことができます。
exit codeのまとめ
プログラムやコマンドが終了したとき、その結果をOS(オペレーティングシステム)に伝えるための数値が「exit code(終了コード)」です。この数値を見ることで、プログラムが「無事に終わったのか」、「何か問題が起きたのか」を判断できます。
| exit code | 意味 |
|---|---|
0 | 成功。処理を続行する |
2 | ブロック。stderr の内容がClaudeへのフィードバックになる |
| その他 | エラーとして扱われるが処理は続行される |
exit code 0 について
exit code が 0 の場合、プログラムやコマンドは「成功した」と判断されます。これは、意図した処理が全て問題なく完了し、次の処理へ進む準備ができていることを意味します。ほとんどのプログラムは、正常に動作を終えると 0 を返します。このコードが返された場合、システムは処理を安心して続行します。
exit code 2 について
exit code が 2 の場合、これは特別な意味を持つブロックの状況を示します。「ブロック」とは、プログラムの実行が一時的に中断されたり、特定の条件によって処理が制限されたりすることを指します。
この場合、「stderr の内容がClaudeへのフィードバックになる」とあります。stderr(スタンダードエラー)は、プログラムがエラーメッセージなどの診断情報を出力するための場所です。ここでいう「Claudeへのフィードバック」とは、プログラムの実行中に発生した特定の問題に関する情報が、AIであるClaudeのシステムへ送られ、今後の改善や学習に利用されることを意味します。つまり、2 は、問題が発生し、その情報がさらに分析される必要がある場合に用いられる特別な終了コードです。
exit code その他 について
exit code が 0 でも 2 でもない、その他の数値の場合、それは一般的にエラーとして扱われます。例えば、ファイルが見つからない、権限がない、入力値が不正であるなど、様々な問題が考えられます。
しかし、「エラーとして扱われるが処理は続行される」という点が重要です。これは、エラーは発生したものの、そのエラーがシステム全体の動作を停止させるほど致命的ではない、あるいは、エラーが発生しても次の処理に進むように設計されている、ということを意味します。エラーの内容は、通常、画面に表示されるメッセージやログファイルなどで確認できます。このようなコードを受け取った場合は、何らかの問題が発生したことを認識し、その原因を調査する必要があります。
よくあるトラブルと対処
hookが動かない
hookとは、特定のイベント(例えば、ファイルを保存した時やプログラムを実行する前など)が発生した際に、自動的に実行されるスクリプトのことです。このhookが意図した通りに動かない場合、主に以下の2つの原因が考えられます。
原因①: スクリプトに実行権限がない
スクリプトファイルは、コンピュータに「実行しても良い」という許可(実行権限)が与えられていないと動きません。LinuxやmacOSなどのシステムでは、ファイルに対して読み込み、書き込み、実行といった権限を設定します。hookスクリプトを実行するには、この実行権限が必要になります。
1chmod +x .claude/hooks/run-linter.sh
このコマンドは、指定されたスクリプトファイル(ここでは.claude/hooks/run-linter.sh)に実行権限を追加します。chmodはファイルの権限を変更するコマンドで、+xは実行権限(eXecutable)を追加するという意味です。
原因②: jq がインストールされていない
jqは、JSON形式のデータをコマンドラインで処理するためのツールです。hookスクリプトの中で、JSON形式のデータ(例えば、プログラムの実行結果や設定情報など)を読み込んだり、加工したりする処理が含まれている場合があります。このjqがシステムにインストールされていないと、スクリプトがjqを使おうとしたときにエラーとなり、hookが正常に動作しなくなります。
1sudo apt install jq
このコマンドは、DebianやUbuntuなどのLinuxディストリビューションで、jqパッケージをインストールするためのものです。sudoは管理者権限でコマンドを実行するために使われ、apt installはソフトウェアをインストールするコマンドです。
ブロックされない
プログラムやシステムで「ブロックされる」とは、特定の条件が満たされた場合に、その後の処理の実行を停止させることを指します。例えば、何らかのチェックでエラーが見つかった場合に、それ以上処理を進めないようにする目的で「ブロック」という仕組みが使われます。
原因: exit 2 ではなく exit 1 になっている
スクリプトの実行を終了させる際にexitコマンドが使われます。exitコマンドの後ろに数字(終了ステータス)を付けることで、スクリプトがどのように終了したかを示すことができます。
一般的に、exit 0は「正常終了」、exit 1は「一般的なエラーで終了」を意味します。
しかし、特定のシステムやhookの仕組みでは、exit 2という終了ステータスが「ブロックする」という特別な意味として扱われることがあります。そのため、処理をブロックしたい場合は、必ずexit 2を使用してスクリプトを終了させる必要があります。
→ ブロックするには必ず exit 2 を使う。
スクリプトのパスが見つからない
スクリプトのパスとは、スクリプトファイルがコンピュータ上のどこに保存されているかを示す場所の情報(ファイルパス)のことです。プログラムがスクリプトを実行しようとしたとき、指定されたパスにファイルが見つからないと、エラーが発生します。
原因: $CLAUDE_PROJECT_DIR が解決できていない
$CLAUDE_PROJECT_DIRのような文字列は「環境変数」と呼ばれ、システムやプログラムが利用できる一時的な情報(ここではプロジェクトのルートディレクトリのパス)を保持しています。この環境変数が何らかの理由で正しく設定されていないか、またはhookスクリプトが実行される環境でこの変数が認識されていない場合、スクリプトはその変数が示すパスを解決できず、「スクリプトが見つからない」というエラーになります。
→ command のパスをプロジェクトルートからの絶対パスに変更して確認する。
この問題を解決するには、$CLAUDE_PROJECT_DIRのような環境変数に頼らず、スクリプトの完全な場所(絶対パス)を直接指定する方法があります。絶対パスは、ファイルシステムの一番上(ルートディレクトリ)から目的のファイルまでの経路をすべて記述したパスのことです。これにより、環境変数の設定に左右されずに、常に正しいスクリプトの場所を特定できるようになります。
おわりに
この記事を通して、AIがコードを操作する特定のタイミングでプログラムを自動実行する「Hooks」の仕組みを理解できたことでしょう。コードの品質チェックを自動化する例や、.envファイルのような重要ファイルを保護する方法を具体的な設定とともに学びました。HooksスクリプトはPythonを含む様々な言語で記述可能であり、特にexit 2という終了コードを返すことでAIの操作をブロックできる点が重要です。もしHooksが意図通りに動作しない場合は、スクリプトの実行権限やjqコマンドのインストール、終了コードの設定などを確認することが解決の鍵となります。Hooksを使いこなすことで、開発プロセスをより効率的かつ安全に進めることができるようになるでしょう。