Claude Codeセルフホスト環境をラッパースクリプトとフックで拡張する
Claude Codeセルフホスト環境のセッションは、ラッパースクリプトとcheckout/post-session/commandの3つのフックで挙動を差し替えられます。環境変数と設定手順を確認します。
ラッパースクリプトとは、Runnerが本来Claude Codeを起動する場所に割り込ませる、自前の実行可能ファイルです。セッションごとに1回呼ばれ、認証情報の発行などを済ませてから、execでRunner自身のバイナリへ処理を渡します。
Claude Codeセルフホスト環境のRunnerは、設定なしでもリポジトリのクローン・Claude Codeの起動・後片付けを自動でこなします。ただし認証情報の発行元を変えたい、クローン処理そのものを差し替えたい、未コミットの作業をセッション終了時に必ず退避したいといった要件は、既定の動きだけでは満たせません。Runnerにはこれを埋める拡張ポイントが4つあります。セッションの実行そのものを乗っ取るラッパースクリプトと、パイプラインの特定の段階だけを差し替える3種類のライフサイクルフック(checkout / post-session / command)です。クイックスタートで最小構成のRunnerを動かした後、何を差し替えたいかで使い分けます。
差し替えたいものから拡張ポイントを選ぶ
ラッパースクリプトとライフサイクルフックは、いずれもRunnerホスト上で実行される実行可能ファイルです。対応OSはLinuxとmacOSで、本記事のサンプルはPOSIXシェルを前提にしています。
| 差し替えたいもの | 使う拡張ポイント | 向いている用途 |
|---|---|---|
| Claude Code本体の起動そのもの | 使う拡張ポイントラッパースクリプト | 向いている用途セッションごとの認証情報発行・ツールチェーン準備 |
| クローン・フェッチ処理 | 使う拡張ポイントcheckoutフック | 向いている用途ミラー経由のクローン・独自git認証・非gitソース |
| 後片付けの前の退避処理 | 使う拡張ポイントpost-sessionフック | 向いている用途未コミット作業の退避・ログのアーカイブ |
| 子プロセス起動処理 | 使う拡張ポイントcommandフック | 向いている用途ラッパーと同じ用途をフックディレクトリに集約 |
設定方法は2通りです。フックのディレクトリは--hooks-dir <path>(環境変数SELF_HOSTED_RUNNER_HOOKS_DIR)で指定します。ラッパーは--exec-path <path>(環境変数SELF_HOSTED_RUNNER_EXEC_PATH)で指定します。フックディレクトリには決まった名前(checkout / post-session / command)の実行可能ファイルを置きます。存在するものだけが呼ばれ、置かなかったフックは組み込みの既定動作にフォールバックします。同じディレクトリに置くspawn-runnerは、セッション単位ではなくオンデマンドRunnerの起動を担う別枠のフックです。
フックもラッパーもRunnerの権限で動き、セッションの子プロセスも同じUIDを共有します。フックディレクトリ、ラッパー、ホストの~/.claude/は読み取り専用でマウントするか、イメージに焼き込んでください。書き込める状態のままだと、セッション側のコードが次のセッション以降の実行内容を書き換えられます。
なお、ここで扱う「ライフサイクルフック」はClaude Code Hooksとは別物です。Claude Code Hooksはセッションの内側で、モデルの応答やツール呼び出しに反応して動きます。ライフサイクルフックはセッションの外側で、Runnerがセッションを起動・終了する過程に反応して動きます。実行される場所も渡される環境変数も別です。
1セッションの間にRunnerが実行する順序
- 1
checkout
リポジトリごとに1回。
checkoutフックがあれば、組み込みのクローン・フェッチの代わりに実行されます。 - 2
子プロセスの起動
セッションごとに1回。
--exec-pathのラッパー、なければcommandフック、どちらも無ければRunner自身のバイナリが起動します。 - 3
post-session
子プロセスが終了した後、作業領域を片付ける前にセッションごとに1回。成否はセッションの結果に影響しません。
ラッパースクリプトでセッションごとの認証情報を発行する
ラッパースクリプトは、Runnerが本来Claude Code本体を起動する場所に割り込ませる実行可能ファイルです。使い捨ての認証情報の発行、環境固有のシークレットのエクスポート、言語ツールチェーンの準備、子プロセスへのリソース制限といった、Runner単体では済ませられないセットアップに使います。最後はexecでRunner自身のバイナリに処理を渡し、シグナルと終了コードを正しく伝えます。
claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret \
--exec-path /etc/claude/session-wrapper.shRunnerはラッパーの環境に多くの変数を渡します。主なものは次のとおりです。
| 変数 | 内容 |
|---|---|
CLAUDE_CODE_SESSION_ACCESS_TOKEN | 内容sk-ant-cc-で始まるセッションJWT。actクレームがセッション作成者を示します。ラッパーが見られるのは起動時点の値だけで、更新後の値は子プロセスの標準入力で届きます |
CLAUDE_RUNNER_CLAUDE_BIN | 内容Runner自身のClaude Codeバイナリへの絶対パス。exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"で締めくくり、PATH解決のclaudeではなくこのバイナリに処理を渡します |
CCR_SESSION_ACCOUNT_EMAIL | 内容セッション作成者のメールアドレスを、署名検証なしでRunnerが事前抽出した値。コミットのトレーラーなどラベリング用途に留めます。トークンにメールが無ければ未設定で、個人情報として扱います |
CLAUDE_CODE_OAUTH_TOKEN | 内容モデル推論とファイルアップロードにのみ有効な、寿命約30分の短命OAuthトークン。ログに残さず、ディスクに書かず、コンテナ外へ転送しません |
CLAUDE_SESSION_INGRESS_TOKEN_FILE | 内容常に最新のセッションJWTを保持するファイルへの絶対パス。添付ファイルのダウンロード時の認証に使われます。execなら引き継がれますが、ラッパーが子プロセスの環境を作り直す方式だと、添付ファイルのダウンロードがエラーなく止まります |
CLAUDE_CODE_REMOTE_MCP_CONFIG | 内容管理側が配信したMCPサーバー定義のファイルパス。ラッパーが環境を作り直すなら引き継ぎます。落とすと、配信の更新時に--mcp-configで足したサーバーが止まります。v2.1.299以降 |
CLAUDE_CODE_REMOTE_SESSION_ID | 内容cse_...形式のセッションID。ライフサイクルフックのCLAUDE_RUNNER_SESSION_ID(session_...形式)と同じセッションです |
CLAUDE_RUNNER_CLIENT_PLATFORM | 内容セッションを作った画面の種別(web_claude_ai・desktop_app・iosなど)。利用状況の集計とラベル用で、認可には使いません。v2.1.229以降で、記録が無ければ未設定です |
ANTHROPIC_BASE_URLは通常https://api.anthropic.comで、上書きしないでください。推論に使う認証情報はAnthropicが発行したOAuthトークンで、他のプロバイダーは受け付けません。セルフホスト環境の推論を別の送信先へ向けることはできません。
未設定の変数とクォートでスクリプトを落とさない
CCR_SESSION_ACCOUNT_EMAILとCLAUDE_RUNNER_CLIENT_PLATFORMは、未設定のことがあります。set -uを使うスクリプトは、未設定の変数を展開した時点でunbound variableで止まります。"${CCR_SESSION_ACCOUNT_EMAIL:-}"のようにデフォルト値を付けて展開します。
Runnerが渡す引数と変数は、すべてクォートします。JSONや文の全体、Bash(npm run test *)のような権限ルールが入ることがあるためです。evalやsh -cの文字列へ値を直接埋め込まず、文字列の側から変数を参照させます。
作成者ごとの認証情報を出し分ける
セッション作成者ごとに認証情報を出し分けたい場合は、decode-tokenサブコマンドでJWTのクレームを読みます。次の例は、人間が作成したセッションだけを対象に、Anthropicのユーザーidから短命のAWS認証情報を発行してからClaude Codeを起動します。
#!/bin/bash
CREATOR_SUB=$("$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token \
| jq -re '.act.sub // "" | select(startswith("user:"))') \
|| { echo "decode-token: verification failed or no human creator" >&2; exit 1; }
creds=$(your-sts-helper assume-role --subject "$CREATOR_SUB") \
|| { echo "credential exchange failed" >&2; exit 1; }
eval "$creds"
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"組織のサービスアイデンティティが作るセッションはact.subがuser:ではなくagent:で始まります。botやClaude Tagチャンネルのセッションが該当し、このスクリプトはそれらを拒否します。同じ環境でそれらも動かすなら、デフォルトの認証情報へ落とすのか拒否するのかを明示的に決めます。
jq -rではなくjq -reを使うのがポイントです。-rだけだとクレームが無いとき文字列nullがそのまま返り、認可チェックを素通りします。メールアドレスで認証情報を引く場合は.act.emailを読み、欠けたときの処理も書きます。メールは作成元の画面が記録していた場合だけトークンに入り、CLIから起票したセッションには無いことがあります。decode-tokenが確認するクレームの範囲は、セッション身元をJWTで検証するで扱っています。
標準入力とファイルディスクリプタ3を切らない
子プロセスの標準入力は、Runnerの制御チャネルです。トークンの更新とセッション終了の合図がここを通ります。ファイルディスクリプタ3はRunnerが開くパイプで、子プロセスの活動シグナルを読み、アイドルと起動タイムアウトの判定に使います。単純なexecならどちらも自動的に保たれます。
終了時の後始末(teardownのtrapなど)のためにバックグラウンド化がどうしても必要なら、標準入力をファイルディスクリプタ4以上に退避してから再接続します。ファイルディスクリプタ3は閉じず、再利用もしません。標準出力と標準エラーのリダイレクトは問題ありません。
exec 4<&0
"$CLAUDE_RUNNER_CLAUDE_BIN" "$@" <&4 4<&- &
CHILD=$!
trap 'teardown' EXIT
wait "$CHILD"システムプロンプトのフラグは落とさない
v2.1.281以降のRunnerは、サーバーが送るシステムプロンプトを一時ファイルに書きます。そのパスを--system-prompt-file <path>と--append-system-prompt-file <path>でラッパーに渡します。v2.1.281より前は、本文を--system-prompt <text>と--append-system-prompt <text>で渡していました。
ラッパーが"$@"を落とすか書き換えると、セッションはサーバーの指示なしで動きます。落とし穴はもう1つあります。各フラグは値を1つしか取らず、最後に書いたものが勝ちます。"$@"の後ろに自前の--append-system-prompt-fileを足すと、サーバーの追加指示は置き換わります。サーバーの指示の上に自社の指示を重ねたいときは、Runnerのイメージに入れたCLAUDE.mdに書きます。Runnerが各セッションのユーザー設定へ展開します。
checkoutフックでクローン処理を差し替える
checkoutフックは、リポジトリごとに1回、Runner組み込みのクローン・フェッチ処理の代わりに実行されます。読み取り専用ミラー経由でクローンする、アーカイブから作業ツリーを復元する、セッションごとのgit認証を適用するといった用途に使います。
| 変数 | 内容 |
|---|---|
CLAUDE_RUNNER_REPO_URL | 内容クローン対象のリポジトリURL。--git-host-rewriteと--git-ssh-rewriteの適用後の値 |
CLAUDE_RUNNER_REPO_REF | 内容チェックアウトするブランチ・タグ・コミットSHA。空ならデフォルトブランチ |
CLAUDE_RUNNER_CHECKOUT_PATH | 内容作業ツリーを残す絶対パス |
CLAUDE_RUNNER_SESSION_ID | 内容session_...形式のセッションID。ログの突き合わせ用 |
CLAUDE_RUNNER_API_BASE_URL | 内容セッションスコープのAPI呼び出し先。JWKSはこの配下で引きます |
CLAUDE_CODE_SESSION_ACCESS_TOKEN | 内容セッションスコープのAPI呼び出し用トークン |
表にないCLAUDE_RUNNER_変数が設定されることもあります。
スクリプトはCLAUDE_RUNNER_CHECKOUT_PATHに、要求されたリビジョンでチェックアウト済みの作業ツリーを残す必要があります。detached HEADで構いません。Runnerがその後、セッションの作業ブランチを積み上げます。
Runnerは後からパスに.gitが存在するかを検証します。Perforceや展開済みtarballのようなgit以外のソースを使うときは、Runnerの環境にCLAUDE_RUNNER_SKIP_GIT_VERIFY=1を設定してこの検証を止めます。ただし作業ブランチの作成や結果のpushにはgitのチェックアウトが要ります。git以外のツリーから成果を持ち出すには、後述のpost-sessionフックを使います。
Runnerはgit認証情報をこのフックに渡しません。基本形は、CLAUDE_CODE_SESSION_ACCESS_TOKENをCLAUDE_RUNNER_API_BASE_URL配下のJWKSエンドポイントに対してJWTライブラリで検証する方法です。得られた作成者の身元から、クローン用の短命認証情報を発行します。checkoutフックの環境にはCLAUDE_RUNNER_CLAUDE_BINが設定されないため、decode-tokenサブコマンドは使えません。ホストにすでにあるSSHエージェントやcredential helper、.netrcにフォールバックする選択肢もあります。
フックが非ゼロで終了した場合、または成功終了しても使えるチェックアウトを残さなかった場合の挙動は、リポジトリの性格で変わります。
| リポジトリの性格 | 挙動 |
|---|---|
| セッションが結果をpushする対象 | 挙動セッションを失敗させ、非ゼロ終了時はスクリプトのstderr末尾をユーザーに表示 |
| セッションが読み取るだけの追加リポジトリ | 挙動[runner:warn]ログを残し、Skippedステップをセッションに投稿し、フックが残したものを消して残りのリポジトリで継続。消せなければセッション終了時に再試行。リポジトリが1つも残らないならセッションを失敗させる |
v2.1.228より前は、リポジトリの性格を問わずフック失敗でセッションを失敗させていました。読み取り専用リポジトリをフックが提供できないだけで、Runnerを移るたびにセッションが失敗し続けていたということです。チェックアウトのパスはセッション終了後にRunnerが削除します。
post-sessionフックで未コミットの作業を退避する
post-sessionフックは、Claude Codeの子プロセスが終了した後・Runnerが作業領域を片付ける前に、セッションごとに1回実行されます。子プロセスを起動したセッションなら、終了の理由を問わず発火します。未コミットの作業を保存できる唯一のタイミングです。--capacityが1を超える構成ではフック終了直後にセッションごとのワークツリーが削除されます。--capacity 1では次のセッション開始時に、使い回すクローンがハードリセットされます。どちらの経路でも、未コミットのトラッキング済みの変更はフックが動く間しか生き残りません。
| 変数 | 内容 |
|---|---|
CLAUDE_RUNNER_EXIT_REASON | 内容セッションが終了した理由 |
CLAUDE_RUNNER_WORKSPACE_PATHS | 内容セッションの作業ツリーの絶対パス(コロン区切り)。リポジトリなしのセッションでは空 |
CLAUDE_RUNNER_SESSION_ID | 内容session_...形式のセッションID |
CLAUDE_RUNNER_DEBUG_LOG_PATH | 内容セッションのデバッグログのパス。フックの実行中はまだディスクにあります |
CLAUDE_RUNNER_EXIT_REASONは4つの値のいずれかです。
| 値 | 意味 |
|---|---|
completed | 意味クリーンな終了。実行中にセッションがアーカイブ・削除された場合も含む |
failed | 意味子プロセスのクラッシュ、またはスポーン後のセットアップ失敗 |
interrupted | 意味アイドルによる解放・起動タイムアウト・サーバーの割り当て解除・ドレイン・--kill-session-after-minの上限超過 |
abandoned | 意味別のRunnerがセッションを引き取った場合用に予約されている値で、この経路でフックが発火することはない |
Runnerのメトリクスは、解放・起動タイムアウト・サーバー側の割り当て解除をcompletedとして数えます。フックのinterruptedと食い違うので、フックの記録とカウンターを突き合わせると完了数が少なく出ます。セッション単位の保証にはフックを、全体の割合にはカウンターを使います。
突然停止と強制終了ではフックが動かない
フックが発火しないのは、VMのプリエンプションや電源断のようにRunner自体が突然止まったときです。突然停止でも退避を保証したいなら、セッション内のClaude Code PostToolUseフックで定期的にスナップショットを取る運用を併用します。
突然停止でなくても、実行中のフックが途中で止められる経路が3つあります。
実行中のpost-sessionフックが止まる3つの経路
フック自身のタイムアウト
--post-session-hook-timeout-sec(既定60秒)を超えると、Runnerがフックのプロセスグループ全体にSIGTERMを送り、2秒後にSIGKILLを送ります。フックが起動したtarやrsyncやgitも一緒に終わります。ただし、自分の出力をファイルへリダイレクトしてSIGTERMの段階を越えて残った子プロセスは、Runnerの手が届きません。ドレイン中の2回目のSIGTERM
Runnerが強制終了します。実行中のフックには何も送られず、素のホストでは最後まで動き続けますが、タイムアウトの管理からは外れます。閉じたログパイプへの書き込みでSIGPIPEを受けて死ぬこともあるため、出力はファイルへリダイレクトしておきます。コンテナ構成ではRunnerがPID 1なので、Runnerの終了とともにコンテナごと終わります。
cgroup単位の強制終了
systemdの
KillMode=control-group(既定)と、terminationGracePeriodSecondsの期限切れでKubernetesが送るSIGKILLは、どちらもフックに届きます。猶予時間をドレイン全体の所要時間以上に取るしか防ぐ手段がありません。
ドレイン全体の所要時間は既定で80秒で、Runnerが起動時にログへ出します。--push-outcome-on-releaseを付けると、さらに30秒延びます。--session-stop-grace-sec(既定5秒)はClaude Codeの子プロセスが自力で終わるのを待つ時間で、フックはその後に走ります。子プロセスのSessionEndフックが長引くなら、この値を上げます。環境変数名はSELF_HOSTED_RUNNER_SESSION_STOP_GRACE_MSで、フラグは秒、環境変数はミリ秒の単位です。--post-session-hook-timeout-secの環境変数SELF_HOSTED_RUNNER_POST_SESSION_HOOK_TIMEOUT_MSも同様にミリ秒です。
退避スクリプトの例
次の例は、未コミットの変更をレスキューブランチにpushします。
#!/usr/bin/env bash
set -u
IFS=':'
g() { git -c core.fsmonitor=false -c core.hooksPath=/dev/null \
-c commit.gpgsign=false "$@"; }
for ws in $CLAUDE_RUNNER_WORKSPACE_PATHS; do
cd "$ws" 2>/dev/null || continue
[ -z "$(g status --porcelain 2>/dev/null)" ] && continue
g add -A
g commit -q -m "runner snapshot: $CLAUDE_RUNNER_SESSION_ID ($CLAUDE_RUNNER_EXIT_REASON)" || continue
g push -q origin "HEAD:refs/heads/rescue/$CLAUDE_RUNNER_SESSION_ID" || true
done-c core.fsmonitor=falseや-c core.hooksPath=/dev/nullは、セッションが.git/configに仕込んだ設定がフックの権限でコードを実行するのを防ぎます。commit.gpgsign=falseは、--configure-gitを付けたRunnerで署名の失敗を避けるためのものです。ただしリポジトリ側のcredential.helperとpushurlは、この例でも効いたままです。フックの終了ステータスはセッションの結果に影響せず、失敗はログに残って無視されます。
pushに使う認証情報は、Runnerホストの環境にすでにあるものがそのまま使われます。イメージに長期認証情報を焼き込まない構成や、組み込みクローンがAnthropicのgitプロキシを経由する構成では使える認証情報がありません。その場合はフック内でCLAUDE_CODE_SESSION_ACCESS_TOKENを検証し、そこから短命のpush用認証情報を発行します。フックがセッション側にない認証情報を持つなら、originをオペレーター指定のURLに置き換え、-c credential.helper=を付けて自前のhelperを渡します。
--push-outcome-on-releaseを付けると、Runnerが主導するセッション終了のときに、成果ブランチをoriginへpushしてから削除します。ただしcheckoutフックで取得したリポジトリは対象外です。これらのスナップショットはpost-sessionフックの仕事になります。pushの分だけ、前述のドレイン所要時間が30秒延びます。
セッションの解放とフックの順序
解放されたセッションは別のRunnerで再開できます。v2.1.236以降のRunnerでは、解放のきっかけによって、フックが終わる前に再開できるかどうかが変わります。
解放のきっかけで変わるフックと再開の順序
ターン後のアイドル・起動タイムアウト
Runnerが子プロセスを止め、フックを最後まで実行してからセッションを解放します。フックの実行中に届いたユーザーメッセージは、フックが終わるまで別のRunnerで再開できません。
権限確認プロンプトなどユーザーの応答待ち
Runnerが先にセッションを解放し、その後でフックを実行します。フックの実行中に届いたユーザーメッセージで、別のRunnerが再開できます。
この順序はアイドルタイムアウト、--retire-atの時刻、v2.1.260以降の--kill-session-after-minの上限のどれで解放する場合にも当てはまります。ターンが終わっていてバックグラウンドタスクだけが残るセッションは、アイドルとして扱われます。v2.1.236より前は、どちらの経路でも先にセッションを解放してからフックを実行していました。SIGTERMによるドレイン中は、フックが終わるまでRunnerがセッションのリースを持ち続けます。
フック内のgitはRunnerが設定を固定する
checkoutとpost-sessionのフックは、セッションのアクセストークンを環境に持ったまま動きます。一方、フック内のgitは、セッションが書き換えられる~/.gitconfigやチェックアウトの.git/configを読みます。そこでv2.1.280以降のRunnerは、フックの実行前にgit設定を環境変数の形で固定します。環境変数で渡した設定は、どの設定ファイルよりも優先されます。起動時には[runner:git] lifecycle hooks:の行に、有効なhooksPath・許可プロトコル・gpgプログラム・署名モードが出ます。
| 項目 | フック内のgitでの固定値 |
|---|---|
core.hooksPath | フック内のgitでの固定値指定がなければ/dev/null。リポジトリの.git/hooksと~/.gitconfigが指すディレクトリのgitフックは動かない |
core.fsmonitor | フック内のgitでの固定値空。設定ファイルが指すモニタープログラムは動かない |
GIT_ALLOW_PROTOCOL | フック内のgitでの固定値https:http:ssh。ローカルパス・file://・git://のクローンはfatal: transport 'file' not allowedなどで失敗する |
core.sshCommand・core.askPass | フック内のgitでの固定値設定ファイルの値は無視。変えるならRunnerの環境のGIT_SSH_COMMAND・GIT_ASKPASSで指定する |
| 署名 | フック内のgitでの固定値--configure-gitがあればセッションとして署名。なければcommit.gpgsign・tag.gpgsignはfalse |
固定値を変えるには、Runnerの環境にGIT_CONFIG_KEY_nとGIT_CONFIG_VALUE_nのペアを書き、GIT_CONFIG_COUNTに個数を入れます。ペアは0から数えます。countが示す最後のペアが欠けていると、Runnerはあなたのペアをすべて無視し、起動時に[runner:warn]を出します。フック内のgit -cはこのペアより強く働きますが、GIT_ALLOW_PROTOCOL・GIT_SSH_COMMAND・GIT_ASKPASSは変えられません。
Runnerが設定しない項目、たとえばcredential helperやurl.*.insteadOf、フィルタードライバーは、セッションが書ける設定ファイルからも読まれます。pushの宛先を変えられることもあります。v2.1.280より前のRunnerは、これらの設定を何も固定しません。
commandフックと--exec-pathの使い分け
commandフックは、checkoutの後・セッションごとに1回、Runner組み込みの子プロセス起動処理の代わりに実行されます。受け取る環境変数はラッパースクリプトと同じで、同様に"$CLAUDE_RUNNER_CLAUDE_BIN"へexecで処理を渡します。カスタマイズを1つのフックディレクトリにまとめたいならcommandフック、ラッパーを別の場所に置きたいなら--exec-pathを使います。両方を置くと意図しない側が動いたままになることがあるので、どちらか一方に統一します。
PATH解決のclaudeではなく、必ずRunner自身のバイナリをexecしてください。Runnerはセッション内の自動更新を止めているため、Runnerのバイナリを使えば、ホストやイメージに入れたバージョンのままセッションが動きます。PATH上の別のclaudeに渡すと、本番運用で説明したバージョン固定が効かなくなります。
手元のclaudeで確認できる設定の既定値
claude self-hosted-runner --helpを、v2.1.287で実行すると、この記事のフラグの既定値を実物で確かめられます。拡張ポイントに関わる行は次のとおりです。
--exec-path <path> Binary to spawn for child sessions. Default: this process's own binary.
--hooks-dir <path> Directory of lifecycle hook scripts (checkout, command, post-session).
Absent hooks fall through to built-in behavior.
--session-stop-grace-sec <n> ... The post-session hook runs after this. Default: 5.
--post-session-hook-timeout-sec <n> SIGTERM budget for the post-session lifecycle hook, on every session
end including runner shutdown. Default: 60.--helpにはフックの置き場所と既定値のほか、--remove-session-stateの説明もあります。v2.1.268以降で使えるフラグです。付けないと、セッションごとのClaude設定ディレクトリ(会話の写し・シェルのスナップショットなどが入る)と、フックがチェックアウトした作業ディレクトリが、<base-dir>/_sessions/に残ります。同じベースディレクトリを使う後続のセッションから読める状態です。有効にすると、失敗・中断したセッションのデバッグログもディスクに残りません。post-sessionフックでデバッグログを手元へ退避するなら、削除の前にこのフックでCLAUDE_RUNNER_DEBUG_LOG_PATHをコピーします。
まとめ
選定の決め手は、退避にかかる時間です。post-sessionフックに許されるのは既定で60秒で、これを延ばすとドレイン全体の所要時間と、必要な猶予時間も延びます。コミット済みの成果は--push-outcome-on-releaseに任せ、フックは未コミット分に絞ると、猶予を短く保てます。