ykich — notes
ArticlesTags
~/articles/claude-code-hooks-formatter

© 2026 ykich

CONTENTS

  • 前提・環境
  • PostToolUse フックの仕組み
  • 設定ファイルの全文
  • フォーマッタースクリプト
  • Biome を使う場合
  • 動作確認
  • 落とし穴と対処法
  • 1. stdout に余分なテキストを出力すると JSON パースが壊れる
  • 2. prettier がグローバル PATH から見えない場合がある
  • 3. exit 1 と exit 2 は別物
  • 4. $CLAUDE_PROJECT_DIR のパスにスペースが含まれる場合
  • 次のアクション

Claude Code の Hooks で保存前フォーマッターを自動実行する

2026-04-17·ykich·#coding#software#claude#ai

Claude Code にコードを書いてもらった直後、prettier を手動で走らせる手間が地味に積み重なります。Claude が生成するコードはフォーマットルールをほぼ守りますが、インデントや末尾セミコロンがわずかにズレることがあります。

PostToolUse フックを使えば、ファイルへの書き込みや編集が完了するたびにフォーマッターが自動実行されます。この記事では設定の全文と、実際に詰まった落とし穴を共有します。

前提・環境

  • Claude Code( 2.1.112)
  • Node.js v22 以上(npx が使える状態)
  • Prettier 3.x または Biome(グローバルインストール不要)

プロジェクト内に prettier がインストールされている場合はそちらが優先されます。グローバルインストールがなくても npx prettier で動作します。

PostToolUse フックの仕組み

Claude Code の Hooks は、セッションのライフサイクル上の特定ポイントでシェルコマンドを自動実行する機能です(公式ドキュメント)。フック型は command / http / prompt / agent の 4 種類があります。

フォーマット自動化に使うイベントは PostToolUse。ツールが正常完了した後に発火します(出典)。Write や Edit ツールへのマッチャーを設定しておくと、Claude がファイルを書き出すたびにスクリプトが動きます。

フックスクリプトは stdin でイベント情報を JSON として受け取ります。Write ツールの場合、tool_input.file_path に書き込まれたファイルのパスが格納されています(出典)。この値を jq で取り出してフォーマッターに渡すのが基本の流れです。

設定ファイルの全文

.claude/settings.json(プロジェクト内)に以下を追記します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

$CLAUDE_PROJECT_DIR はプロジェクトルートを指す環境変数です。パスにスペースが含まれるケースに備え、ダブルクォートで囲んでいます(出典)。"Write|Edit" は | 区切りで複数ツールを指定する書き方です(出典)。

フォーマッタースクリプト

.claude/hooks/format.sh を次の内容で作成します。

# macOS / Linux / Node.js v22.17.0
#!/bin/bash
FILE=$(jq -r '.tool_input.file_path' < /dev/stdin)

EXT="${FILE##*.}"
case "$EXT" in
  ts|tsx|js|jsx|mjs|cjs)
    npx prettier --write "$FILE" 2>/dev/null
    ;;
esac

exit 0

jq -r '.tool_input.file_path' で stdin の JSON からファイルパスを取り出せることは検証済みです。npx prettier はグローバルインストールがない環境でも動作することを確認しています(Prettier 3.8.3)。

実行権限を付与します。

chmod +x .claude/hooks/format.sh

Biome を使う場合

Prettier を Biome に置き換える場合は format --write に差し替えます。

# macOS / Linux / Node.js v22.17.0
#!/bin/bash
FILE=$(jq -r '.tool_input.file_path' < /dev/stdin)
EXT="${FILE##*.}"

case "$EXT" in
  ts|tsx|js|jsx)
    npx biome format --write "$FILE" 2>/dev/null
    ;;
esac

exit 0

動作確認

Claude Code でプロジェクトを開き、フォーマットが崩れた状態でファイルを書かせてみます。「test.ts に const x=1 と書いて」と指示し、書き込まれたファイルが const x = 1; になっていれば成功です。

なお、$CLAUDE_PROJECT_DIR が実際のプロジェクトルートを正しく指すかどうか、および npx prettier が PostToolUse から問題なく呼び出せるかは、お使いの環境での手元確認が必要です。詳しい確認手順は verification/manual-steps.md を参照してください。

落とし穴と対処法

1. stdout に余分なテキストを出力すると JSON パースが壊れる

Claude Code は exit 0 のとき stdout を JSON としてパースしようとします。スクリプト内に echo でデバッグ出力を入れると、JSON でない文字列が stdout に流れてパースエラーになります(検証済み)。デバッグ出力は必ず >&2(stderr)に向けてください。

# ❌ stdout に出力するとパース失敗
echo "Processing: $FILE"
exit 0

# ✅ stderr に向ける
echo "Processing: $FILE" >&2
exit 0

~/.bashrc や ~/.zshrc が起動時に何かを echo している場合も同様にパースが壊れます。シェルプロファイルの出力は抑制するか、スクリプト内で bash --noprofile --norc を使って起動してください。

2. prettier がグローバル PATH から見えない場合がある

フックスクリプトが実行されるときの PATH は、通常のターミナルセッションとは異なることがあります。prettier が直接解決できない場合は npx prettier を使います。npx 自体も解決できない場合は絶対パスで指定します。

# npx の絶対パスを確認する
which npx  # 例: /Users/yuki/.nvm/versions/node/v22.17.0/bin/npx

3. exit 1 と exit 2 は別物

PostToolUse フックで exit 2 を返しても、ツールの実行はブロックできません。ツールはすでに完了しているためです(出典)。exit 2 を返した場合は stderr の内容が Claude へのエラーコンテキストとして渡され、Claude が追加処理を試みることがあります。フォーマッタースクリプトでは exit 0 を返すだけで十分です。

4. $CLAUDE_PROJECT_DIR のパスにスペースが含まれる場合

// ❌ スペース入りパスで壊れる
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format.sh"

// ✅ ダブルクォートで囲む
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format.sh"

次のアクション

  • PreToolUse + matcher Bash で rm -rf などの危険コマンドを事前にブロックするフックを追加する
  • Stop フックでセッション終了時に変更ファイルの lint 結果をまとめて表示する
  • .claude/settings.json(チーム共有)と .claude/settings.local.json(個人設定)を分けて管理することで、スクリプト本体はチームで共有しつつ、個人の PATH 設定だけを local に書く構成が実用的です


#coding#software#claude#ai
← Back to notes