8
8

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 活用パターン集 ── ダッシュボード開発で学んだ実践Tips

8
Last updated at Posted at 2026-02-27

はじめに

前回の記事「【完全ガイド】Claude Code Hooks で開発ワークフローを自動化する ── 全14イベント徹底解説」では、Hooks の基本概念と全14イベントを解説した。

イベントの仕様は理解できたが、実際にまとまったものを作ろうとすると「どう設計すればいいか」で手が止まる。筆者自身がそうだった。

そこで、Claude Code のセッションをリアルタイム監視するダッシュボードを実際に開発した。その過程で見えてきた Hooks の設計パターン実装上の注意点を本記事にまとめる。

各パターンは独立しているので、自分のプロジェクトに必要なものだけ拾い読みしてほしい。


作ったもの: Claude Code Dashboard

Claude Code のセッション中に発生するイベントを収集し、ブラウザでリアルタイム表示するダッシュボードを開発した。ツール使用統計、タスク進捗、変更ファイル一覧などを可視化できる。

GitHub: nogataka/claude-dashboard

使用した Hook イベントは次の5つ。

  • PreToolUse / PostToolUse -- ツール呼び出しの記録
  • Stop -- 応答完了の検知
  • SessionStart -- セッション開始の記録
  • UserPromptSubmit -- ユーザー入力の記録

以降、このダッシュボード開発で得たパターンを順に紹介する。


パターン 1: 全イベント横断キャプチャ

課題

複数のイベントに対してそれぞれ別のスクリプトを書くと、処理の重複が増え保守が大変になる。

解決策

1つのスクリプトを全イベントに共通で設定する。

イベントの種別は stdin で受け取る JSON の hook_event_name フィールドで判別できる。スクリプト側で分岐すればよい。

~/.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      { "command": "cat | bash /path/to/hook/capture-event.sh" }
    ],
    "PostToolUse": [
      { "command": "cat | bash /path/to/hook/capture-event.sh" }
    ],
    "Stop": [
      { "command": "cat | bash /path/to/hook/capture-event.sh" }
    ],
    "SessionStart": [
      { "command": "cat | bash /path/to/hook/capture-event.sh" }
    ],
    "UserPromptSubmit": [
      { "command": "cat | bash /path/to/hook/capture-event.sh" }
    ]
  }
}

イベントの流れ

メリット

  • スクリプトの修正が1箇所で済む
  • ログフォーマットが統一される
  • 新しいイベントの追加が settings.json の1行で完了する

応用

この「1スクリプト多イベント」パターンは、以下の用途にそのまま使える。

用途 概要
ログ収集 全イベントを時系列で記録する
監査証跡 誰がいつ何を実行したかを残す
デバッグ イベントの発火順序を確認する
メトリクス イベント数を集計して傾向を見る

パターン 2: stdin パイプでの JSON 受け取り

cat | bash script.sh の意味

settings.json の command に書く cat | bash script.sh は、2つの処理を繋いでいる。

  1. cat -- stdin から Hook の入力 JSON を読み取る
  2. | bash script.sh -- その内容をパイプでスクリプトに渡す

Claude Code は Hook の入力データを stdin に JSON として流す。この cat | がないとスクリプト側で stdin を受け取れない。

stdin からの JSON 読み取り

スクリプトの冒頭で、まず stdin を変数に格納する。

capture-event.sh
#!/bin/bash

# stdin から JSON を読み取る(最初に必ず行う)
INPUT="$(cat)"

# jq が使える場合
if command -v jq >/dev/null 2>&1; then
  SESSION_ID="$(echo "$INPUT" | jq -r '.session_id // empty')"
  HOOK_EVENT="$(echo "$INPUT" | jq -r '.hook_event_name // empty')"
  CWD="$(echo "$INPUT" | jq -r '.cwd // empty')"
else
  # jq がない環境でのフォールバック
  SESSION_ID="$(echo "$INPUT" | grep -o '"session_id"\s*:\s*"[^"]*"' \
    | head -1 | sed 's/.*"\([^"]*\)"$/\1/')"
  HOOK_EVENT="$(echo "$INPUT" | grep -o '"hook_event_name"\s*:\s*"[^"]*"' \
    | head -1 | sed 's/.*"\([^"]*\)"$/\1/')"
  CWD="$(echo "$INPUT" | grep -o '"cwd"\s*:\s*"[^"]*"' \
    | head -1 | sed 's/.*"\([^"]*\)"$/\1/')"
fi

注意点

INPUT="$(cat)" は必ずスクリプトの冒頭で実行する。stdin は一度しか読めないため、後から読み直すことはできない。

jq なしのフォールバックは応急処置として使う。ネストされた JSON やエスケープ文字を含む値では正しく動作しない可能性がある。本格運用では jq のインストールを推奨する。


パターン 3: セッション単位のデータ分離

なぜセッションで分離するのか

Claude Code は複数のターミナルで同時に実行できる。全イベントを1つのファイルに書き込むと、異なるセッションのデータが混在し分析が困難になる。

実装パターン

session_id をファイル名に使い、セッションごとにファイルを分離する。

capture-event.sh
STATE_DIR="${CLAUDE_DASHBOARD_DIR:-$HOME/.claude/dashboard-events}"
mkdir -p "$STATE_DIR"

# セッションIDでファイルを分離
LOG_FILE="$STATE_DIR/${SESSION_ID}.jsonl"

ファイル構造のイメージ

~/.claude/dashboard-events/
  abc123-def456.jsonl    # セッション A のイベント
  ghi789-jkl012.jsonl    # セッション B のイベント
  mno345-pqr678.jsonl    # セッション C のイベント

複数セッション同時実行での安全性

セッションごとにファイルが分かれるため、複数の Claude Code が同時に動いても書き込みが衝突しない。JSONL 形式(後述)との組み合わせで並行書き込みの安全性がさらに高まる。


パターン 4: JSONL による追記型ログ

なぜ JSON 配列ではなく JSONL か

イベントログの保存形式として JSON 配列と JSONL(JSON Lines)の2つが考えられる。

形式 書き込み方法 並行安全性 読み取り
JSON 配列 [{...}, {...}] ファイル全体を読み書き 低い パース必須
JSONL(1行1オブジェクト) >> で追記 高い 1行ずつ処理可

JSONL は各行が独立した JSON オブジェクトなので、>> での追記だけで済む。ファイル全体をロックする必要がない。

実装

capture-event.sh
# タイムスタンプを付与して JSONL として追記
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"

if command -v jq >/dev/null 2>&1; then
  echo "$INPUT" | jq -c --arg ts "$TS" '. + {_ts: $ts}' \
    >> "$STATE_DIR/${SESSION_ID}.jsonl"
else
  # jq なしの場合: 末尾の } を置換してタイムスタンプを付与
  echo "$INPUT" | sed "s/}$/,\"_ts\":\"$TS\"}/" \
    >> "$STATE_DIR/${SESSION_ID}.jsonl"
fi

ポイント

  • jq -c で1行にコンパクト化する(JSONL の要件)
  • _ts フィールドでイベント受信時刻を付与する
  • >> による追記はアトミック性が高い(1行が短い場合)

出力される JSONL の例

abc123-def456.jsonl
{"session_id":"abc123","hook_event_name":"SessionStart","cwd":"/home/user/project","_ts":"2026-02-27T10:00:00Z"}
{"session_id":"abc123","hook_event_name":"PreToolUse","tool_name":"Read","_ts":"2026-02-27T10:00:05Z"}
{"session_id":"abc123","hook_event_name":"PostToolUse","tool_name":"Read","_ts":"2026-02-27T10:00:06Z"}
{"session_id":"abc123","hook_event_name":"PostToolUse","tool_name":"Edit","_ts":"2026-02-27T10:00:15Z"}
{"session_id":"abc123","hook_event_name":"Stop","_ts":"2026-02-27T10:00:30Z"}

パターン 5: PostToolUse からツール使用統計を取る

PostToolUse イベントの構造

PostToolUse の入力 JSON には以下のフィールドが含まれる。

フィールド 内容
tool_name ツール名(Read, Edit, Bash など)
tool_input ツールへの入力パラメータ
tool_response ツールの実行結果

ツール別の集計

JSONL に記録した PostToolUse イベントを集計すると、Claude がどのツールをどれだけ使ったかが分かる。

src/dashboard.ts
interface ToolStats {
  [toolName: string]: number;
}

function aggregateToolUsage(events: HookEvent[]): ToolStats {
  const tools: ToolStats = {};
  let errors = 0;

  for (const ev of events) {
    if (ev.hook_event_name === 'PostToolUse' && ev.tool_name) {
      tools[ev.tool_name] = (tools[ev.tool_name] || 0) + 1;

      // エラーの検出
      if (ev.tool_response?.error || ev.tool_response?.type === 'error') {
        errors++;
      }
    }
  }

  return tools;
}

変更ファイルの追跡

tool_input の中身はツールによって異なる。ファイル操作系のツールでは file_pathpath フィールドが含まれる。

src/dashboard.ts
function trackModifiedFiles(events: HookEvent[]): string[] {
  const files = new Set<string>();

  for (const ev of events) {
    if (ev.hook_event_name !== 'PostToolUse') continue;

    const input = ev.tool_input;
    if (!input) continue;

    // Edit, Write, Read などのファイルパスを収集
    const filePath = input.file_path || input.path;
    if (filePath && typeof filePath === 'string') {
      files.add(filePath);
    }
  }

  return Array.from(files);
}

統計データの活用例

こうした統計は Claude の作業パターンの理解に役立つ。Read が多ければ調査中心のセッション、Edit が多ければ実装中心のセッションだと判断できる。


パターン 6: タスクとユーザー質問のトラッキング

Claude Code のタスク管理ツール

Claude Code には TaskCreateTaskUpdate という内部ツールがある。Claude がタスクを作成・更新するたびに PostToolUse イベントが発火する。

タスクの状態遷移を追跡する

PostToolUsetool_nameTaskCreateTaskUpdate のイベントを監視すると、タスクの進捗を追跡できる。

src/dashboard.ts
interface TaskInfo {
  id: string;
  subject: string;
  status: 'pending' | 'in_progress' | 'completed';
  updatedAt: string;
}

function trackTasks(events: HookEvent[]): TaskInfo[] {
  const tasks = new Map<string, TaskInfo>();

  for (const ev of events) {
    if (ev.hook_event_name !== 'PostToolUse') continue;

    if (ev.tool_name === 'TaskCreate') {
      const input = ev.tool_input;
      if (input?.subject) {
        tasks.set(input.subject, {
          id: input.subject,
          subject: input.subject,
          status: 'pending',
          updatedAt: ev._ts || '',
        });
      }
    }

    if (ev.tool_name === 'TaskUpdate') {
      const input = ev.tool_input;
      if (input?.taskId && input?.status) {
        const existing = tasks.get(input.taskId);
        if (existing) {
          existing.status = input.status;
          existing.updatedAt = ev._ts || '';
        }
      }
    }
  }

  return Array.from(tasks.values());
}

タスクの状態遷移

ユーザー質問の記録

UserPromptSubmit イベントを記録しておくと、セッション中にユーザーが何を指示したかの履歴が残る。

同様に、PostToolUsetool_nameAskUser のイベントを捕捉すると、Claude からユーザーへの質問も記録できる。タスクの文脈とユーザーの指示を合わせて見ることで、セッション全体の流れを把握しやすくなる。


パターン 7: cwd ベースのプロジェクト分類

cwd フィールドの活用

すべての Hook イベントの入力 JSON には cwd(Current Working Directory)フィールドが含まれる。これをプロジェクトの識別子として使える。

capture-event.sh
CWD="$(echo "$INPUT" | jq -r '.cwd // empty')"
PROJECT_NAME="$(basename "$CWD")"

マルチプロジェクト対応

複数のリポジトリで同時に Claude Code を使う場合、cwd でプロジェクトを自動分類できる。

ダッシュボードでの実装

ダッシュボードでは全セッションの cwd を収集してユニークなプロジェクト一覧を生成し、タブで切り替えられるようにした。

src/dashboard.ts
function getProjects(sessions: SessionData[]): string[] {
  const projects = new Set<string>();

  for (const session of sessions) {
    for (const event of session.events) {
      if (event.cwd) {
        projects.add(event.cwd);
      }
    }
  }

  return Array.from(projects);
}

cwd はフルパスで長くなるため、表示時には basename でディレクトリ名だけにすると見やすい。


パターン 8: async: true でパフォーマンスに影響しない Hook

async: true とは

Hook の設定に "async": true を指定すると、Hook がバックグラウンドで実行される。Claude Code は Hook の完了を待たずに次の処理に進む。

~/.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "command": "cat | bash /path/to/capture-event.sh",
        "async": true
      }
    ]
  }
}

使うべきケース

ケース async の推奨
ログ記録、メトリクス収集 true
外部 API への通知送信 true
ツール実行の許可/拒否判定 false
フォーマッタの自動実行 false

判断基準はシンプル。「結果で Claude の動作を制御する必要があるか」 で決める。

ダッシュボードのようなイベント記録用途では Hook の結果が Claude の動作に影響しない。このケースでは async: true で Claude の処理速度に影響を与えないようにする。

注意点

async: true の Hook では、exit code による制御(exit 2 でのブロック)が機能しない。JSON 出力による permissionDecision なども無視される。制御が必要な Hook には async: true を使わないこと。

全イベント非同期キャプチャの設定例

ダッシュボード用途に適した設定を示す。全イベントを非同期でキャプチャする。

~/.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "command": "cat | bash /path/to/hook/capture-event.sh",
        "async": true
      }
    ],
    "PostToolUse": [
      {
        "command": "cat | bash /path/to/hook/capture-event.sh",
        "async": true
      }
    ],
    "Stop": [
      {
        "command": "cat | bash /path/to/hook/capture-event.sh",
        "async": true
      }
    ],
    "SessionStart": [
      {
        "command": "cat | bash /path/to/hook/capture-event.sh",
        "async": true
      }
    ],
    "UserPromptSubmit": [
      {
        "command": "cat | bash /path/to/hook/capture-event.sh",
        "async": true
      }
    ]
  }
}

Tips: よくあるトラブルと対策

.bashrc / .zshrc の echo が JSON を壊す

Hook スクリプトは bash で実行されるため、.bashrc が読み込まれる場合がある。.bashrc 内の echo やプロンプト設定が stdout に出力されると、Hook の JSON 出力に混入してパースエラーになる。

対策:

capture-event.sh
#!/bin/bash --norc
# --norc で .bashrc の読み込みをスキップする

INPUT="$(cat)"
# ... 以降の処理

もしくは settings.json 側で bash --norc を明示する。

{
  "command": "cat | bash --norc /path/to/capture-event.sh"
}

jq がインストールされていない環境

CI 環境やコンテナ内では jq がない場合がある。パターン 2 で紹介した grep/sed によるフォールバックを入れておくと安全。

ただしフォールバックには限界がある。プロジェクトの要件に応じて以下の方法も検討する。

方法 長所 短所
grep/sed フォールバック 依存なし 複雑な JSON に非対応
Python ワンライナー 多くの環境に入っている 起動が少し遅い
jq の事前インストール 確実 セットアップが必要

Python を使ったフォールバックの例を示す。

capture-event.sh
if command -v jq >/dev/null 2>&1; then
  SESSION_ID="$(echo "$INPUT" | jq -r '.session_id // empty')"
elif command -v python3 >/dev/null 2>&1; then
  SESSION_ID="$(echo "$INPUT" | python3 -c \
    "import sys,json; print(json.load(sys.stdin).get('session_id',''))")"
else
  SESSION_ID="$(echo "$INPUT" | grep -o '"session_id"\s*:\s*"[^"]*"' \
    | head -1 | sed 's/.*"\([^"]*\)"$/\1/')"
fi

JSONL ファイルが肥大化した場合

長時間のセッションや大量の Hook イベントで JSONL ファイルが大きくなることがある。

対策 1: セッション開始時に古いファイルを削除する

cleanup-old-events.sh
#!/bin/bash --norc
# 7日以上前のイベントファイルを削除
find "${CLAUDE_DASHBOARD_DIR:-$HOME/.claude/dashboard-events}" \
  -name "*.jsonl" -mtime +7 -delete

これを SessionStart Hook に設定すると、セッション開始時に古いログが自動で掃除される。

対策 2: ログローテーション

capture-event.sh
LOG_FILE="$STATE_DIR/${SESSION_ID}.jsonl"
MAX_SIZE=10485760  # 10MB

# ファイルサイズが上限を超えたらローテーション
if [ -f "$LOG_FILE" ]; then
  FILE_SIZE="$(stat -f%z "$LOG_FILE" 2>/dev/null \
    || stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)"
  if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
    mv "$LOG_FILE" "${LOG_FILE}.$(date +%s).bak"
  fi
fi

Hook スクリプトのデバッグ方法

Hook が期待どおりに動かないとき、以下の手順で調査する。

手順 1: stdin のダンプ

まず Hook に渡される JSON の中身を確認する。

debug-hook.sh
#!/bin/bash --norc
INPUT="$(cat)"
echo "$INPUT" >> /tmp/hook-debug.log

手順 2: verbose モードでの実行

Claude Code を --verbose フラグ付きで起動すると Hook の実行結果が表示される。

claude --verbose

手順 3: 単体テスト

Hook スクリプトを直接実行してテストする。

echo '{"session_id":"test-123","hook_event_name":"PostToolUse","tool_name":"Edit","cwd":"/home/user/project"}' \
  | bash /path/to/capture-event.sh

デバッグ用の Hook は本番運用前に必ず削除するか、async: true にして影響を最小限にする。


まとめ

本記事で紹介した8つのパターンを一覧にまとめる。

パターン 概要 主な用途
1. 全イベント横断キャプチャ 1スクリプトで全イベントを処理 ログ収集、監視
2. stdin パイプでの JSON 受け取り cat | bash で入力を安全に処理 全 Hook 共通
3. セッション単位のデータ分離 session_id でファイルを分離 並行実行対応
4. JSONL による追記型ログ 1行1オブジェクトで追記 構造化ログ
5. PostToolUse からツール統計 tool_name で集計 使用分析
6. タスクとユーザー質問の追跡 TaskCreate/Update を監視 進捗管理
7. cwd ベースのプロジェクト分類 cwd でプロジェクト判別 マルチプロジェクト
8. async: true の活用 非同期でパフォーマンス維持 記録系 Hook

これらのパターンはダッシュボードに限らず、さまざまな Hooks プロジェクトに応用できる。自分のワークフローに合ったパターンを組み合わせて活用してほしい。

関連リンク

8
8
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
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?