0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code Hooksで開発品質を自動担保する - PreToolUse/PostToolUseの実践

0
Last updated at Posted at 2026-03-11

Hooksとは何か

Claude CodeのHooksは、AIがツールを実行する直前・直後に自動でスクリプトを走らせる仕組みだ。

例えば:

  • コードを書いた後 → 自動でフォーマッター実行
  • bashコマンド実行前 → 危険なコマンドをブロック
  • ファイル編集後 → lintチェックを走らせる

人間がClaude Codeと並んで「formatterかけて」「lintチェックして」と言わなくても、勝手にやってくれる。


設定場所

Hooksは .claude/settings.json(プロジェクト固有)または ~/.claude/settings.json(全プロジェクト共通)に書く。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/check_dangerous.py"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
          }
        ]
      }
    ]
  }
}

PreToolUse: 実行前フック

危険なコマンドのブロック

PreToolUse はツール実行に走る。終了コード2を返すとツール実行がブロックされる。

# .claude/hooks/check_dangerous.py
import json
import sys

data = json.load(sys.stdin)
tool_name = data.get("tool_name", "")
tool_input = data.get("tool_input", {})

# Bashコマンドのチェック
if tool_name == "Bash":
    command = tool_input.get("command", "")

    BLOCKED_PATTERNS = [
        "rm -rf /",
        "rm -rf ~",
        "DROP TABLE",
        "DROP DATABASE",
        "git push --force origin main",
        "git push --force origin master",
    ]

    for pattern in BLOCKED_PATTERNS:
        if pattern in command:
            print(f"BLOCKED: '{pattern}' は実行が禁止されています", file=sys.stderr)
            print(f"コマンド: {command}", file=sys.stderr)
            sys.exit(2)  # 2でブロック

sys.exit(0)  # 0で許可

終了コードの意味

コード 意味
0 許可(続行)
2 ブロック(ツール実行を停止)
その他 警告(ログに記録、実行は続行)

PostToolUse: 実行後フック

ファイル保存後に自動フォーマット

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/auto_format.py"
          }
        ]
      }
    ]
  }
}
# .claude/hooks/auto_format.py
import json
import subprocess
import sys
from pathlib import Path

data = json.load(sys.stdin)
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")

if not file_path:
    sys.exit(0)

path = Path(file_path)

if path.suffix in (".py",):
    subprocess.run(["ruff", "format", str(path)], check=False)
    subprocess.run(["ruff", "check", "--fix", str(path)], check=False)
elif path.suffix in (".ts", ".tsx", ".js", ".jsx"):
    subprocess.run(["npx", "prettier", "--write", str(path)], check=False)
elif path.suffix in (".go",):
    subprocess.run(["gofmt", "-w", str(path)], check=False)

sys.exit(0)

これで、Claude Codeがコードを書くたびに自動的にフォーマットがかかる。


実践的なHooks設定集

セキュリティスキャン(ファイル書き込み後)

# .claude/hooks/security_scan.py
import json
import subprocess
import sys

data = json.load(sys.stdin)
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")

if not file_path or not file_path.endswith(".py"):
    sys.exit(0)

# シークレットパターンのスキャン
import re

SECRET_PATTERNS = [
    r"sk-ant-api\d{2}-[a-zA-Z0-9_-]{86}",  # Anthropic
    r"AKIA[0-9A-Z]{16}",                     # AWS
    r"ghp_[a-zA-Z0-9]{36}",                  # GitHub
    r"sk_(live|test)_[a-zA-Z0-9]{24}",       # Stripe
]

with open(file_path, encoding="utf-8", errors="replace") as f:
    content = f.read()

found = []
for pattern in SECRET_PATTERNS:
    matches = re.findall(pattern, content)
    found.extend(matches)

if found:
    print(f"WARNING: シークレットの可能性あり: {found}", file=sys.stderr)
    # 警告のみ(ブロックしない場合はsys.exit(0))
    # ブロックする場合は sys.exit(2)

sys.exit(0)

テスト自動実行(実装ファイル変更時)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/run_tests.py",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
# .claude/hooks/run_tests.py
import json
import subprocess
import sys
from pathlib import Path

data = json.load(sys.stdin)
tool_input = data.get("tool_input", {})
file_path = tool_input.get("file_path", "")

if not file_path:
    sys.exit(0)

path = Path(file_path)

# src/ 以下のPythonファイルのみ
if "src" not in path.parts or path.suffix != ".py":
    sys.exit(0)

# 対応するテストファイルを探す
test_path = Path("tests") / f"test_{path.name}"
if not test_path.exists():
    sys.exit(0)

result = subprocess.run(
    ["pytest", str(test_path), "-q", "--no-header"],
    capture_output=True,
    text=True
)

print(result.stdout, file=sys.stdout)
if result.returncode != 0:
    print(result.stderr, file=sys.stderr)
    # テスト失敗時は警告のみ(ブロックは任意)

sys.exit(0)

環境変数

フックスクリプト内では以下の環境変数が使える。

変数 内容
CLAUDE_PROJECT_DIR プロジェクトのルートディレクトリ
CLAUDE_TOOL_NAME 実行されたツール名
CLAUDE_TOOL_INPUT_FILE_PATH 操作されたファイルパス(ファイル操作系のみ)
CLAUDE_TOOL_INPUT_COMMAND 実行されたコマンド(Bashのみ)

まとめ

Hooksを使うと、Claude Codeの出力を「必ず品質チェック済み」の状態にできる。

Hook おすすめ設定
PreToolUse(Bash) 危険コマンドブロック
PostToolUse(Write/Edit) 自動フォーマット、シークレットスキャン
PostToolUse(Write) テスト自動実行

人間が毎回「フォーマットして」「テストして」と言わなくて済む分、Claude Codeとの作業がスムーズになる。


このような自動化をカスタムスキルとして提供しています。

Security Pack(¥1,480) では、シークレット検出・OWASP診断・CVEチェックをスキルとして実行可能です。

みょうが (@myougaTheAxo) — ウーパールーパーのVTuber。Claude Codeの実践的な使い方を発信中。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?