1
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 で mv・sed の上書きを防ぐ設定例と全スクリプト

1
Last updated at Posted at 2026-07-02

Claude Code hooks の実装ガイド——PreToolUse で mv・sed の上書きを防ぐ設定例と全スクリプト

claude-code-hooks-design

Claude Code を使っていると「CLAUDE.md に書いたのに守ってくれない」という経験を一度はするはずです。

その根本的な解決策として hooks があります。この記事では、実際に使えるスクリプトと settings.json の設定例をそのまま掲載します。設計の考え方については元記事(Claude Code の hooks はなぜ CLAUDE.md や指示文では代替できないのか)を参照してください。


hooks とは——1行で説明すると

Claude Code がツールを実行する直前・直後に、シェルスクリプトを自動実行できる仕組み。LLM を介さないので確定的に動く。

設定ファイルの場所

.claude/
├── settings.json       ← hooks をここに書く(リポジトリ共有)
└── hooks/
    ├── check-mv-overwrite.sh
    ├── check-cp-overwrite.sh
    └── check-sed-awk-inplace.sh

.claude/settings.json をコミットすると、このリポジトリを触るすべてのエージェント・メンバーに自動適用されます。

settings.json の基本構成

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/check-mv-overwrite.sh"
          },
          {
            "type": "command",
            "command": "bash .claude/hooks/check-sed-awk-inplace.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/check-process-completion.sh"
          }
        ]
      }
    ]
  }
}

matcher はツール名(Bash / Write / Edit)または *(全ツール)を指定します。

exit code と permissionDecision の早見表

返り値 動作
exit 0 通過(ツールをそのまま実行)
exit 2 + stderr 出力 Claude へのフィードバック付きでブロック
permissionDecision: "ask" ユーザーへの確認プロンプトを表示
permissionDecision: "deny" ツール呼び出しをキャンセルして Claude に理由を送る

hooks スクリプトは 標準入力から JSON を受け取りpermissionDecision を含む JSON を標準出力に返すか、exit code で返します。

スクリプト①: mv の上書きを防ぐ(check-mv-overwrite.sh)

#!/usr/bin/env bash
# PreToolUse: mv コマンドで移動先ファイルが存在する場合に確認プロンプトを出す

set -euo pipefail

INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

# mv コマンドでなければ素通し
if [[ ! "$CMD" =~ ^[[:space:]]*mv([[:space:]]|$) ]]; then
  exit 0
fi

# コマンドをトークン分割して最後の引数を DEST とみなす
read -ra TOKENS <<< "$CMD"
DEST="${TOKENS[-1]}"

# オプションフラグは除外
while [[ "$DEST" == -* ]]; do
  DEST="${TOKENS[-2]}"
  TOKENS=("${TOKENS[@]:0:${#TOKENS[@]}-1}")
done

# 移動先が既に存在する場合のみ確認
if [[ -e "$DEST" ]]; then
  cat <<JSON
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",
    "permissionDecisionReason": "上書き警告: $DEST が既に存在します。上書きしますか?"
  }
}
JSON
fi

exit 0

スクリプト②: sed の in-place 編集にバックアップを強制(check-sed-awk-inplace.sh)

#!/usr/bin/env bash
# PreToolUse: バックアップなしの sed -i を検知して確認プロンプトを出す

set -euo pipefail

INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

emit_ask() {
  cat <<JSON
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "ask",
    "permissionDecisionReason": "$1"
  }
}
JSON
  exit 0
}

# sed が含まれていなければ素通し
if [[ ! "$CMD" =~ sed ]]; then
  exit 0
fi

# -i.<拡張子> 形式(-i.bak 等)はバックアップあり → 素通し
if [[ "$CMD" =~ -i\.[a-zA-Z0-9_-]+ ]]; then
  exit 0
fi

# -i '' (macOS) または -i (GNU) はバックアップなし → 確認
if [[ "$CMD" =~ sed.*-i[[:space:]]+\'\' ]] || [[ "$CMD" =~ sed.*-i[[:space:]] ]]; then
  emit_ask "sed -i はバックアップなしの in-place 編集です。-i.bak を推奨します。実行しますか?"
fi

exit 0

スクリプト③: 未完了プロセスがあればセッション終了をブロック(check-process-completion.sh)

#!/usr/bin/env bash
# Stop hook: 進捗ファイルに未完了チェックリストが残っていたら終了をブロック

PROGRESS_DIR="${CLAUDE_PROJECT_DIR:-.}/.claude/progress"

if [[ ! -d "$PROGRESS_DIR" ]]; then
  exit 0
fi

for file in "$PROGRESS_DIR"/*.md; do
  [[ -e "$file" ]] || continue
  UNCHECKED=$(grep -n "^- \[ \]" "$file" 2>/dev/null || true)
  if [[ -n "$UNCHECKED" ]]; then
    echo "⚠️  未完了のプロセスステップがあります: $file" >&2
    echo "$UNCHECKED" >&2
    exit 2
  fi
done

exit 0

スクリプトに実行権限を付ける

chmod +x .claude/hooks/*.sh

マルチエージェント構成での使い方

.claude/settings.json をコミットするだけで、秘書エージェント・エンジニアエージェント・マーケターエージェントなどすべてのサブエージェントに同一のガードレールが適用されます。個々のエージェント定義に同じルールを書く必要はありません。

エージェントA ──┐
エージェントB ──┼──→ PreToolUse hook ──→ check-mv-overwrite.sh ──→ ブロック or 通過
エージェントC ──┘

CLAUDE.md に書いたルールはエージェントが「読んで判断する」(確率的)のに対し、hooks は「ランタイムが直接実行する」(確定的)という点が本質的な違いです。


元記事

Claude Code の hooks はなぜ CLAUDE.md や指示文では代替できないのか——マルチエージェント環境での安全設計3原則

設計の考え方(なぜ CLAUDE.md では代替できないのか)・ブロック vs 警告の判断基準・タイムアウト設計については元記事で詳しく解説しています。

1
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
1
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?