Claude CodeのSessionStart hookが発火しないとき——matcherと実行ディレクトリの落とし穴

SessionStart hookが発火しない原因 AI関連

SessionStart hook を設定したのに、何も起きない。エラーも出ない。そういうときに疑う場所は2つあります。

最小の設定

まず、確実に動く形はこれです。

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command", "command": "bash /absolute/path/to/script.sh" }
        ]
      }
    ]
  }
}

置き場所は次のいずれかです。

ファイル 適用範囲
~/.claude/settings.json すべてのプロジェクト
<プロジェクト>/.claude/settings.json そのフォルダだけ
<プロジェクト>/.claude/settings.local.json そのフォルダだけ・共有しない

原因1:matcher が実際の source と一致していない

matcher省略できます。 省略するか、"" または "*" にすると、すべてのセッション開始で発火します。

値を書いた場合は、その値と一致したときだけ発火します。

matcher 発火する場面
startup 新規セッション
resume --resume--continue/resume
clear /clear
compact 自動または手動の圧縮
fork --fork-session/fork/branch

問題は、アプリの画面で「新しいスレッド」を立てたときに、それが startup として扱われるとは限らないことです。

実測では、デスクトップアプリでスレッドを開いたときに resume として発火した例がありました。"matcher": "startup" と書いていると、この場合は素通りします。

自分の環境の source を調べる

hook は起動時にJSONを標準入力で受け取ります。 その中に source が入っているので、それを記録すれば実際の値が分かります。

#!/bin/bash
cat > "$HOME/hook-source.json"

これを matcher なしで登録し、セッションを開き直してから ~/hook-source.json を見ます。

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "hook_event_name": "SessionStart",
  "source": "startup"
}

この source の値が、書くべき matcher です。

判断に迷うなら、matcher を書かないのが確実です。 すべての開始で発火します。

原因2:相対パスで書いている

hook は「そのときの作業ディレクトリ」で実行されます。この作業ディレクトリは一定ではありません。

実測では、同じ設定から発火した hook の作業ディレクトリが、C:\Users\<ユーザー>C:\Users\<ユーザー>\<プロジェクト> に分かれました。

そのため、こう書くと落ちます。

# 悪い例:どこに書かれるか分からない
echo "$(date)" >> .claude/hook.log

読み込むファイルも、書き込むファイルも、絶対パスで指定します。

# 良い例
echo "$(date)" >> "$HOME/hook.log"

相対パスのまま「ファイルが無い」を見ると、発火していないと誤読します。 実際は発火していて、別の場所に書かれているだけ、ということが起こります。

発火したかどうかの確かめ方

会話で聞いても証拠になりません。

やりがちなのが、hook に echo 合言葉 と書いて、新しいセッションで「合言葉、見えてる?」と聞く方法です。この聞き方では、質問文に合言葉が入っているため、それを読んだだけで「見えています」と答えられます。

確実なのは、会話の外にあるものに書かせることです。

#!/bin/bash
echo "$(date) | PWD=$(pwd)" >> "$HOME/hook-fired.log"

セッションを開き直したあとにファイルを見て、行が増えていれば発火しています。誰かの返答を信じる必要がありません。

終了コードの意味

終了コード 動き
0 成功。標準出力の内容がそのままコンテキストに入ります
2 ブロッキングエラー。標準エラー出力がエラー通知として表示されます
その他 非ブロッキングエラー。標準エラー出力の1行目が表示されます

SessionStart は、標準出力がコンテキストに入る数少ないイベントのひとつです。日付や現在の状況を流し込みたい場合は、単に echo するだけで届きます。

タイムアウトの既定は600秒です。timeout フィールドで変更できます。

既知の不具合

公式リポジトリに報告が上がっているものです。当てはまる場合、設定が正しくても動きません。

  • /clearSessionStart が発火しないIssue #34072
  • VS Code拡張で /clear したとき、sourceclear ではなく startup になるIssue #26794)。"matcher": "clear" は一致しません
  • Windowsでプラグイン由来の hook が実行されないIssue #21468
  • プラグインの読み込みより先に hook が走り、起動時にエラーが出るIssue #19491

確認の手順

  1. matcher を消す。すべての開始で発火する形にする
  2. 書き込み先を絶対パスにする
  3. ファイルに書かせて確かめる。会話で聞かない
  4. 発火を確認できたら、source を記録して、必要なら matcher を戻す
タイトルとURLをコピーしました