SessionEnd(セッションエンド)

フック
SessionEnd
セッションエンド
Claude Codeのセッションが終わる瞬間に、決めたコマンドを自動で走らせるためのフック。下書きのバックアップ、裏で動いてる確認用サーバーの停止、会話ログのアーカイブなど「終了時の片付け」専用に使う。標準出力はAIには渡らない、終了をブロックすることもできない、片付け専用の差し込み口

Claude Codeのセッションを閉じる時に「あれ、さっきの会話どこまで保存したっけ」となりがちな人向け

Claude Code を閉じるたびに毎回同じ片付け(下書きバックアップ、SessionStart で起動したサーバーの停止、会話ログのアーカイブ)を手作業でやっていて忘れがちな時に、settings.json の hooks.SessionEnd に1本スクリプトを登録して自動化する場面で使う

Claude Codeのウィンドウを閉じた瞬間、書きためたレシピの下書きはそのままパソコンに残り、起動しっぱなしのプレビューサーバーは裏で動き続けます。SessionEnd は「セッションが終わるその瞬間に、決めておいた後片付けを自動で走らせる」ための差し込み口です。

一日の終わりにキッチンを片付けるのと同じで、終わるたびに毎回手で片付けるのは続きません。終了の瞬間に走るスクリプトを1本登録しておけば、下書きのバックアップ、裏で動いてる確認用サーバーの停止、会話履歴のアーカイブまで、毎回同じ手順で片付くようになります。

噛み砕くと、料理屋の閉店作業を自動でやる仕組み

料理屋でいうと「閉店時刻が来たら、レジを締めて、火元を確認して、その日の売上をノートに書き写して、明かりを消して帰る」という決まった片付けの流れがあります。SessionEnd はこの「閉店作業」の差し込み口です。

店主(読者)が手を動かして毎晩同じ手順を踏まなくても、扉を閉めた瞬間に決めた手順が自動で走ります。違うのは1点だけ。閉店の瞬間に書いたメモを、もう次の日のClaudeは読まないということです。あくまで「片付け係」専用で、翌日への申し送りには使えません。

画面に何が出るのか(終了時の挙動)

SessionEnd は通常の作業中には何も出してきません。見えるのは Claude Code が閉じるその瞬間だけです。デバッグ表示を有効にしているとこんな感じで流れます。

$ claude
> レシピの下書きを3本書いてもらった後...
> /logout

[hook] SessionEnd matcher=logout running cleanup.sh
[hook] backup: drafts/*.md -> ~/backup/cooking-blog/2026-05-01/
[hook] killed: hugo server (pid 48213)
[hook] SessionEnd completed in 1.2s
$

注意すべきは、ここで echo した文字列が Claude には届かないことです。SessionStart と正反対の仕様で、SessionEnd の標準出力は AI のコンテキストには一切渡りません。終わるタイミングなので、もう会話する相手がいないからです。

料理ブログ「cooking-blog」を題材に、実際の手順を見る

Hugoという静的サイト生成ツールで料理レシピサイトを作っているという設定で、終了時の片付けを自動化する例を組みます。フォルダの場所は ~/projects/cooking-blog

ステップ1: 何を片付けたいか、3つに分けて書き出す

SessionEnd で走らせる片付けは、毎回同じ手順になるものだけが対象です。今回はこの3つにします。

  • セッション中に書きためた drafts/ 内のレシピ下書きを、日付フォルダに丸ごとコピー
  • SessionStart で起動した hugo server(プレビュー用の常駐プロセス)を停止
  • 会話ログを月別フォルダにアーカイブ

毎回同じ手順だから自動化に向いている、という判断です。

ステップ2: 片付けスクリプトを書く

~/projects/cooking-blog/.claude/hooks/cleanup.sh という場所に、片付けの中身を書いた小さなスクリプトを置きます。

#!/bin/bash
# stdin から JSON を受け取る
INPUT=$(cat)
REASON=$(echo "$INPUT" | python3 -c "import json,sys;print(json.load(sys.stdin)['reason'])")

DATE=$(date +%Y-%m-%d)
MONTH=$(date +%Y-%m)
BACKUP_DIR=~/backup/cooking-blog/$DATE
ARCHIVE_DIR=~/archive/cooking-blog/$MONTH

mkdir -p "$BACKUP_DIR" "$ARCHIVE_DIR"

case "$REASON" in
  logout|other)
    # ログアウトと想定外終了は丸ごとバックアップ
    cp drafts/*.md "$BACKUP_DIR/" 2>/dev/null
    pkill -f "hugo server"
    ;;
  prompt_input_exit)
    # Ctrl+D で抜けた時はバックアップだけ、サーバーは触らない
    cp drafts/*.md "$BACKUP_DIR/" 2>/dev/null
    ;;
  resume)
    # 別セッションへ切り替える瞬間。下書きだけ退避しておく
    cp drafts/*.md "$BACKUP_DIR/" 2>/dev/null
    ;;
  clear|bypass_permissions_disabled)
    # /clear や bypass モード解除はバックアップ不要、ログのアーカイブだけ
    ;;
esac

# 会話ログを月別にアーカイブ(reason 共通)
TRANSCRIPT=$(echo "$INPUT" | python3 -c "import json,sys;print(json.load(sys.stdin)['transcript_path'])")
cp "$TRANSCRIPT" "$ARCHIVE_DIR/" 2>/dev/null

exit 0

reason によって挙動を分岐させているのがポイントです。clear の度に重いバックアップが走ると邪魔だからです。

ステップ3: settings.json に登録する

同じプロジェクトの .claude/settings.json を開いて、SessionEnd の枠にこのスクリプトを差し込みます。

{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/cleanup.sh"
          }
        ]
      }
    ]
  }
}

matcher を "*" にすると全ての終了経路で走ります。特定の経路だけにしたければ "logout""prompt_input_exit" を直接指定。

ステップ4: 動作確認は /logout で十分

登録できたか確かめる一番手早い方法は /logout を一度叩くことです。

$ claude
> /logout
# ここで cleanup.sh が走る

$ ls ~/backup/cooking-blog/$(date +%Y-%m-%d)/
recipe-001-tonjiru.md  recipe-002-katsudon.md  recipe-003-yakisoba.md

$ pgrep -f "hugo server"
# 何も返ってこなければ停止成功

バックアップフォルダに下書きが入っていて、hugo server のプロセスが消えていれば成功です。

仕組み解説(入力JSONと SessionStart との非対称)

SessionEnd のスクリプトには JSON 形式の入力が渡ってきます。中身はこの5項目です。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../projects/.../00893aaf.jsonl",
  "cwd": "/Users/.../projects/cooking-blog",
  "hook_event_name": "SessionEnd",
  "reason": "logout"
}

reason がこの中で一番大事です。終わり方によって取れる値が決まっていて、6種類あります。

reason 発火する場面
clear /clear で会話履歴をリセットした時
resume 別セッションに切り替える瞬間(--resume--continue で別セッションを再開する手前で、いまのセッションが閉じる時)
logout /logout または認証情報が切れた時
prompt_input_exit プロンプト入力中に Ctrl+D(EOF)で抜けた時
bypass_permissions_disabled bypass permissions モード(権限確認をスキップする特殊モード)が無効化された時
other 上記以外の終了経路、想定外の落ち方

そして、SessionStart と SessionEnd は鏡写しのようで実は非対称です。整理するとこうなります。

項目 SessionStart SessionEnd
標準出力がAIに渡るか 渡る 渡らない
exit 2 でブロック可能か 可能(場合により) 不可(無視される)
compact 時に発火するか する(source=compact) しない(PreCompact / PostCompact が別途走る)
--resume 時に発火するか する(source=resume) する(reason=resume、別セッションに切り替える瞬間に走る)

SessionEnd の出力が AI に届かないのは、終わる瞬間に出してももう読み手がいないからです。設計の整合性で割り切られています。

使いどころ3シナリオ

シナリオ1: 下書きを毎回バックアップしたい

料理ブログを書きためている途中で、誤って git checkout .rm -rf drafts/ をしてしまうと、書いた内容が一瞬で消えます。SessionEnd で日付フォルダに丸ごとコピーする運用を入れておくと、最悪でもセッション開始時点までは戻せます。バックアップ先は ~/backup/ のように Git の管理外にしておくのが無難です。

シナリオ2: 起動した確認用サーバーを止めたい

SessionStart で hugo servernpm run dev を裏で起動するワークフローにしている場合、Claude Code を閉じてもプロセスが残り続けます。気づくと裏で5本も6本も同じプロセスが動いていて、これがポート競合やメモリ消費の元です。SessionEnd で pkill -f "hugo server" を1行入れておくだけで解消します。

シナリオ3: 月末まとめ用に会話ログをアーカイブしたい

セッションごとに transcript_path に会話ログがJSONLで残るので、これを月別フォルダ(~/archive/2026-05/ 等)にコピーしておくと、後から「あの時どんな質問をしたか」を月単位で振り返れます。Aisola Lab のような記事執筆ワークフローでは、ネタ帳としても使えます。

初心者が踏みやすい落とし穴

  • 標準出力が AI に渡る前提で書いてしまう。SessionStart の感覚で echo "今日の作業まとめ" を大量に出しても、Claude には1文字も届かない。AIに渡したいなら SessionStart や UserPromptSubmit が正解
  • exit 2 で終了をブロックしようとする。SessionEnd は遮断不可。「保存してない下書きがあるから止める」みたいな防衛動作は機能しない。終了は止められない前提で、後片付けに徹する
  • SessionStart で起動した裏のプログラムを止め忘れるhugo servernpm run devdocker compose up 等。明示的に pkilldocker compose down を書かないと、毎日Claude Codeを開閉するたびに裏のプロセスが増殖する
  • prompt_input_exit 時に重い処理を走らせる。Ctrl+D で抜けた時は「ちょっと中断したいだけ」のことが多い。そこで全件バックアップが走るとキャンセル不能で待たされる。reason で分岐して中断時は最小処理に絞る
  • compact でも SessionEnd が走ると思い込む。実は compact では SessionEnd は発火しない。会話圧縮の最中もセッション自体は続いていて、終了扱いにはならないからです。会話圧縮のたびに毎回片付けたいなら、PreCompact 側にスクリプトを書くのが正解
  • reason 値を4種類だけだと思い込むclear / resume / logout / prompt_input_exit / bypass_permissions_disabled / other の6種類ある。matcher で "logout" だけ拾うつもりで "*" を使ってると、resumebypass_permissions_disabled でも同じ片付けが走って想定外の挙動になる
  • transcript_path を即座に読みに行く。終了時点でも書き込み中の場合があり、最終行が欠けることがある。確実にしたいなら少し sleep を入れるか、コピーだけして後段の処理は別プロセスに任せる
  • 重いバックアップで時間切れになる。タイムアウトは標準600秒だが、フォルダが GB 単位だと境界を超える。重い処理は nohup ... & disown で裏に逃がして、フック自体は即座に終わる構造にする

関連するコマンド・ツールへの動線

書き方

settings.json の hooks.SessionEnd にスクリプトを登録する。matcher で終了理由(clear / resume / logout / prompt_input_exit / bypass_permissions_disabled / other)を絞り込める

やってみるとこうなる

入力

{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/cleanup.sh"
          }
        ]
      }
    ]
  }
}

出力例

[hook] SessionEnd matcher=logout running cleanup.sh
[hook] backup: drafts/*.md -> ~/backup/cooking-blog/2026-05-01/
[hook] killed: hugo server (pid 48213)
[hook] SessionEnd completed in 1.2s

このページに出てきた言葉

SessionEnd
Claude Codeのセッションが終わる瞬間に、決めたコマンドを自動実行する仕組み。フックの種類のひとつ
セッション
Claude Codeを起動してから終了するまでの一連の会話のかたまり
フック
特定のタイミング(セッション開始時、ツール実行前、終了時など)で自動的に走るコマンドを差し込む仕組み
標準出力
コマンドが画面に表示する出力のこと。<code>echo</code> や <code>print</code> で出した文字列はここに流れる
コンテキスト
Claudeがその会話で覚えている情報の範囲。ここに渡された情報をAIは読める
matcher
フックを「どの状況で走らせるか」を絞り込む条件指定。SessionEnd の場合は終了理由(reason)で絞る
reason
セッションがどう終わったかを示す値。clear / resume / logout / prompt_input_exit / bypass_permissions_disabled / other の6種類
compact
会話履歴が長くなった時、Claude Codeが過去のやり取りを要約して圧縮する操作。圧縮中もセッションは続いており、SessionEnd は走らない(PreCompact / PostCompact が代わりに動く)
PreCompact / PostCompact
会話圧縮の直前と直後に走る別のフック。compact のたびに片付けたいなら SessionEnd ではなくこちら側に書く
bypass permissions モード
ツール実行のたびに出る権限確認をスキップする特殊な動作モード。解除すると SessionEnd が <code>bypass_permissions_disabled</code> で走る
常駐プロセス
起動した後、明示的に止めない限り裏で動き続けるプログラム。<code>hugo server</code> や <code>npm run dev</code> が代表例
プロセス
動いているプログラムの実体。1本のコマンドが1つのプロセスを作る
pkill
名前を指定してプロセスを終了させるコマンド。<code>pkill -f "hugo server"</code> は「hugo server を含むプロセスを止める」
stdin
コマンドが受け取る入力データ。Claude Codeのフックは終了情報をJSON形式でここに渡す
$CLAUDE_PROJECT_DIR
Claude Codeが起動時に決めるパソコン側の設定値で、いま開いているプロジェクトのフォルダ位置を指す
EOF
「入力終わり」を意味する合図。ターミナルでは Ctrl+D を押すと送られる
exit code
コマンドが終了する時に返す数字。0が成功、それ以外が異常終了の合図
JSONL
1行に1つのJSONを書き並べた形式。会話ログのように「履歴を時系列で追記する」用途で使われる
タイムアウト
「何秒以内に終わらなかったら強制中断する」の制限時間。SessionEnd は標準600秒
Hugo
Markdown形式の文章から静的なWebサイトを自動生成するツール。料理ブログや技術ブログでよく使われる

関連項目

公式ドキュメント

https://code.claude.com/docs/en/hooks

-

← 戻る