毎晩動かしている自動投稿の処理が、記事を push したあとに .next の削除とログの追記をやり残したまま終わることが、月に数回ありました。生成と push までは確実に走るのに、最後の後片付けだけが抜け落ちる。原因はいつも同じで、後片付けを「本文の処理の続き」として書いていたために、途中で別の分岐に入ると最後まで到達しないことがあったのです。
2026年6月18日の更新で Claude Code にポストセッションフック(SessionEnd)が加わりました。セッションが終わったあとに必ず走るフックです。これは、まさに私が「本文の最後に置いていたから取りこぼしていた処理」を、構造として終了時に保証してくれる仕組みでした。個人開発で複数のサイトを順番に処理している立場から、実際に何を移し、どこでつまずいたかを書いておきます。
後片付けを「処理の続き」に書くと、なぜ取りこぼすのか
セッション本体の処理は、エラーや早期 return、条件分岐で経路が枝分かれします。後片付けをその末尾に置くと、本流を通ったときしか実行されません。シェルスクリプトの trap や、プログラミング言語の finally で守る発想と同じことを、Claude Code のセッションに対してやりたい——それが SessionEnd フックの位置づけです。
私の場合、取りこぼしていたのは次の3つでした。一時ビルド成果物(.next)の削除、その日の更新ログの追記、そして次回のためのディスク空き容量の確認です。どれも記事の品質には関係しませんが、放っておくとディスクが詰まり、翌日のセッションが起動すらしなくなります。
SessionEnd フックの基本形
フックは settings.json の hooks に、イベント名をキーとして登録します。SessionEnd はセッションが閉じる直前に発火します。
{
"hooks" : {
"SessionEnd" : [
{
"matcher" : "*" ,
"hooks" : [
{
"type" : "command" ,
"command" : "$CLAUDE_PROJECT_DIR/.claude/hooks/on-session-end.sh"
}
]
}
]
}
}
ここで一つ覚えておきたいのは、SessionEnd フックの終了コードや標準出力は、すでに終わろうとしているセッションの結論を変えないという点です。Stop フックのように処理をブロックして続行させる用途ではなく、あくまで「終わったあとの確実な一仕事」に向いています。私は最初これを Stop フックと混同して、後片付けスクリプトの中で次の処理を呼び戻そうとして失敗しました。SessionEnd は引き返せない場所だと割り切るのが正解です。
後片付けスクリプトを書く
移したかった3つの処理を、1本のスクリプトにまとめます。標準入力で渡されるセッション情報(JSON)から、作業ディレクトリやセッション ID を受け取れます。
#!/usr/bin/env bash
# .claude/hooks/on-session-end.sh
set -uo pipefail
# セッション情報は stdin に JSON で渡る
PAYLOAD = "$( cat )"
CWD = "$( echo " $PAYLOAD " | python3 -c 'import sys,json; print(json.load(sys.stdin).get("cwd",""))')"
LOG_DIR = "${ HOME }/automation/logs"
mkdir -p " $LOG_DIR "
# (1) 一時ビルド成果物の削除
[ -d "${ CWD }/.next" ] && rm -rf "${ CWD }/.next"
# (2) その日の更新ログを追記(JST で日付を採る)
TODAY = "$( TZ = Asia/Tokyo date +%F)"
echo "$( TZ = Asia/Tokyo date +%T) session ended in ${ CWD }" >> "${ LOG_DIR }/${ TODAY }.txt"
# (3) ディスク空き容量の確認。閾値を割ったら警告だけ残す
FREE_MB = "$( df " $HOME " --output=avail -m | tail -1 | tr -d ' ')"
if [ "${ FREE_MB :- 0 }" -lt 500 ]; then
echo "WARN: free ${ FREE_MB }MB at session end" >> "${ LOG_DIR }/${ TODAY }.txt"
fi
exit 0
ログの日付を TZ=Asia/Tokyo で固定しているのは理由があります。素の date は環境によって UTC を返し、日本時間の深夜に動かしたとき前日のログファイルへ書き込んでしまうことがあるからです。私自身、これで一度ログが1日ずれて、あとから追うのに苦労しました。日付を扱う処理は最初からタイムゾーンを明示しておくと安全です。
4サイト運用で実際に何を移したか
私は Dolice という名前で複数の技術ブログを運営していて、それぞれ別リポジトリを順番に処理しています。これまで各サイトの処理の末尾に、ほぼ同じ後片付けコードを貼り付けていました。同じコードが4箇所に散らばっていると、片方だけ直してもう片方を直し忘れる、という事故が起きます。
SessionEnd フックに寄せたことで、後片付けの実体は1本のスクリプトに集約され、各サイトの処理からは消えました。下の表は、移管の前後で何がどこに置かれていたかを整理したものです。
処理 移管前の置き場所 移管後の置き場所
.next 削除 各サイト処理の末尾(4箇所に重複) SessionEnd フック1本
更新ログ追記 本文処理の途中(分岐で取りこぼし) SessionEnd フック1本
ディスク確認 次回起動時の冒頭 SessionEnd フック1本(早期警告)
重複が消えただけでなく、ディスク確認を「次の起動時」ではなく「今回の終了時」に前倒しできたのが効きました。問題に気づくのが一晩早くなったわけです。
つまずいた点と回避策
移行の途中で踏んだ落とし穴を3つ挙げておきます。本番の自動運用で実際に詰まったものです。
フック内で対話を期待してしまう : SessionEnd はセッションが閉じる場面なので、確認待ちのような対話的な処理を入れても応答は得られません。完全に非対話で完結させる必要があります。
スクリプトの失敗でログが残らない : set -e を入れたまま削除対象が無いケースで途中終了し、肝心のログ追記まで到達しないことがありました。後片付けスクリプトは、個々の処理が失敗しても最後まで走り切るよう、致命的でないエラーは握りつぶす設計にしています(上の例で set -e を使わず set -uo pipefail にしているのはこのためです)。
相対パスでの事故 : フックの実行時の作業ディレクトリは想定とずれることがあります。削除やログのパスは $HOME や stdin から得た cwd を起点に絶対パスで組み立てると、誤った場所を消す事故を防げます。
特に3つ目は、後片付けが「消す」処理である以上、慎重になりすぎて損はありません。私は削除対象を .next のような限定されたディレクトリ名に固定し、ワイルドカードでの一括削除は避けています。
どこまでフックに任せ、どこを手元に残すか
何でもフックに移せばよいわけではありません。私が線引きの基準にしているのは「失敗しても本文の結果を壊さないか」です。後片付け・ログ・空き容量チェックのように、本文の成果(記事の生成と push)が終わったあとに走る冪等な処理は、SessionEnd に置くのが向いています。
逆に、push そのものや品質ゲートの判定のように、結果を左右する処理は本文側に残すべきです。これらは終了コードで成否を判断し、失敗したらやり直す必要があるからです。終了間際のフックに置くと、失敗しても引き返せません。
性質 置き場所 例
冪等・後始末・記録 SessionEnd フック .next 削除、ログ追記、空き容量警告
結果を左右する・やり直しが要る 本文の処理 品質ゲート、git push、件数の整合性確認
この線引きは、Stripe の決済 webhook を扱うときの考え方とも似ています。確実に一度だけ効かせたい結果系の処理と、あとから安全に何度でも走らせられる記録系の処理を分けておくと、運用がずっと楽になります。
フックが発火したか確かめる
登録しても発火していなければ意味がないので、最初は必ず確認します。私は上のスクリプトの先頭に echo "$(TZ=Asia/Tokyo date +%T) hook fired" >> "${LOG_DIR}/${TODAY}.txt" のような一行を仕込み、セッションを一度終えてからログに行が増えるかを見ます。増えていれば登録は効いています。発火しない場合は、settings.json の階層(プロジェクト直下の .claude/settings.json か、ユーザーレベルか)と、スクリプトに実行権限(chmod +x)が付いているかを順に確認すると、たいていそのどちらかが原因です。慣れてきたら確認用の一行は外して構いません。
次の一歩
まずは小さく試すのがおすすめです。いま手元のプロジェクトで、セッションの末尾に「毎回やっているが本質ではない一手間」が一つあれば、それを on-session-end.sh に切り出して SessionEnd に登録してみてください。私の場合は .next の削除という地味な一行から始めました。取りこぼしが消えるだけでも、翌朝の安心感がずいぶん変わります。