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 server や npm 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 server、npm run dev、docker compose up等。明示的にpkillやdocker 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"だけ拾うつもりで"*"を使ってると、resumeやbypass_permissions_disabledでも同じ片付けが走って想定外の挙動になる transcript_pathを即座に読みに行く。終了時点でも書き込み中の場合があり、最終行が欠けることがある。確実にしたいなら少し sleep を入れるか、コピーだけして後段の処理は別プロセスに任せる- 重いバックアップで時間切れになる。タイムアウトは標準600秒だが、フォルダが GB 単位だと境界を超える。重い処理は
nohup ... & disownで裏に逃がして、フック自体は即座に終わる構造にする
関連するコマンド・ツールへの動線
- SessionStart(セッションスタート) - セッションが始まる瞬間に走るフック。SessionEnd の鏡写しだが、こちらは標準出力がAIに渡る
- PreToolUse(プリツールユース) - ツールを実行する直前に走るフック。exit 2 で実行を遮断できる、SessionEnd と違って強い権限を持つ
- PostToolUse(ポストツールユース) - ツール実行後に走るフック。実行ログを取りたい時はこちらが向く
- Hooks(フックス) - フック全体の概念。8種類あるイベントの全体像はここで把握する
- settings.json - フックを登録する場所。プロジェクト単位で
.claude/settings.jsonに書く
書き方
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サイトを自動生成するツール。料理ブログや技術ブログでよく使われる