claude -pをSIGTERMで止めるとどうなる — exit 143とSessionEnd
claude -pにkillやSIGTERMを送ると終了コードは143で、実行中のターンは記録されません。Bashのプロセスツリー、SessionEnd、権限プロンプト、SIGINTとの違いを順に説明します。
claude -pをSIGTERMで止めると、Claude Codeは終了コード143で終了します。実行中だったターンは未完了のまま残り、そのターンの結果は記録されません。ターンを最後まで終わらせて止めたいときは、SIGTERMではなくSIGINTを送ります。
CIのタイムアウトやプロセス管理ツールは、まずSIGTERMで止めにきます。そのときclaude -pの内側で何が起きるかを、停止の方法ごとに切り分けます。
止め方で結果はどう変わるか
停止シグナルごとの違い
SIGTERM(kill)
終了コードは143。実行中のターンは未完了のままで、結果は記録されません。
SIGINT / interrupt()
先に今のターンを終わらせてから止めます。Agent SDKではinterrupt()が同じ役割です。
SIGTERMの扱いは、claude -pの解説ページ内の「Stop a run with SIGTERM」の節に書かれています。要点は次の4つです。
- 終了コードは143(シグナル番号15に128を足した値で、シェルが「SIGTERMで終了した」と読む慣例の値)
- 実行中のBashコマンドは、プロセスツリーごと終了される
- 終了処理中に動くフックは
SessionEndだけ - 新しいツール呼び出しもモデルへのリクエストも始まらない
通常の完走時の終了コードはClaude Code -pモードの基本にまとめています。143はその「0以外」の一種で、認証切れのように結果が標準出力に書き出される失敗とは性質が違います。
実行中のBashコマンドとプロセスツリー
SIGTERMを受けたとき、まだ走っているBashコマンドがあると、Claude Codeはそのプロセスツリーを終了させます。子プロセスや孫プロセスも対象です。テストランナーやビルドが孫プロセスを残して居座る、という状況をClaude Code側が防いでくれます。
終了させたコマンドは、セッション上は「killed」として記録されます。コマンドが完了した扱いにはなりません。
ここで効いてくるのは、ターンの途中で止めたあとの再開です。コマンドが書きかけのファイルを残している可能性は、止めた側が自分で確かめる必要があります。Claude Codeが元に戻すとは書かれていません。
権限プロンプトに答えを待っていたとき
権限プロンプトの回答待ちで止まっていた場合の扱いは、止め方で変わります。
| 止め方 | プロンプトの扱い |
|---|---|
| プロセスへSIGTERMを送る | プロンプトの扱いプロンプトは未回答のまま残る |
| Agent SDK経由でセッションを閉じる | プロンプトの扱いSDKがClaude Codeの入力を先に閉じ、入力が終わった時点でプロンプトはキャンセルされる |
SDKの場合、シグナルを送る前に入力が閉じられます。プロンプトを出さない運用なら、そもそもこの状況は起きにくくなります。誰も答えない実行では--permission-prompts noneを渡すと、プロンプトになる操作は拒否され、Claudeは再試行しないよう伝えられて実行が続きます(v2.1.259以降)。定期ジョブでSIGTERMを使う前に、こちらで待ち自体を無くしておく選択肢があります。
SessionEndだけが走る — 後始末フックの書き方
終了処理中に走るのはSessionEndフックだけです。PreToolUseやStopなどの他のフックは動きません。
後始末をここに集めておけば、SIGTERMで止まった実行でもログを残せます。例えば、終了の事実を追記する設定は次のような形になります。
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "echo \"$(date -u +%FT%TZ) session ended\" >> /tmp/claude-p-end.log",
"timeout": 5
}
]
}
]
}
}注意したいのが時間の予算です。SessionEndフックの既定のタイムアウトは1.5秒で、設定側のフックに長いtimeoutがあれば、予算はそれに合わせて最大60秒まで上がります。環境変数CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MSでも、ミリ秒で明示的に上書きできます。重い後始末を入れるなら、この予算を先に確認してください。
予算の1.5秒が適用される場面として記載があるのは、終了・/clear・対話の/resumeです。SIGTERMで止めたときに同じ予算が使われるかどうかは、SIGTERMの節にもSessionEndの節にも記載がありません。そのため、SIGTERMを送った直後にSIGKILLを送る構成では、フックが終わる前にプロセスごと消える可能性があります。SIGTERMとSIGKILLの間に猶予を置き、その猶予より短いtimeoutでフックを書いておく設計が無難です。猶予の秒数はCIやコンテナ、プロセス管理ツールごとに設定が違うので、使っているツールの設定で確かめてください。
環境変数CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MSの挙動はv2.1.268で変わっています。それ以前は全体の予算だけが上がり、自前のtimeoutを持たないフックは1.5秒で打ち切られました。v2.1.268以降は、指定した値がtimeoutを持たない各フックのタイムアウトにもなります。古いバージョンのまま使うジョブでは、フックごとにtimeoutを書いておくと確実です。
後始末スクリプトが受け取る情報
SessionEndフックには、共通の入力に加えてreasonが渡されます。後始末スクリプトで使えそうなのは、次のフィールドです。
| フィールド | 中身 |
|---|---|
session_id | 中身止めたセッションの識別子。再開に使える |
transcript_path | 中身会話記録のJSONLファイルのパス |
cwd | 中身実行時の作業ディレクトリ |
reason | 中身clear / resume / logout / prompt_input_exit / other |
reasonの値の一覧に、SIGTERMで止めた場合の値は載っていません。SIGTERMのときにどの値になるか不明なので、止めたときの後始末にはmatcherでreasonを絞らず、すべての終了で動くように書くのが確実です。またSessionEndは終了を止められず、systemMessageなどのJSON出力は破棄されます。成果はファイルやログに書き出す形にします。標準出力へ返しても、読む相手がいません。
落とし穴
SIGKILLを直接送ると後始末が走らない可能性がある
SIGKILL直撃の場合のSessionEndの扱いは、SIGTERMの節に記載がありません。エラー解説には、SIGKILLなどで中断されたセッションの後にサンドボックスのプレースホルダーファイルが残る、という説明があります。後始末まで走らせたいなら、SessionEndが走ると明記されているSIGTERMで止めるのが確実です。
SIGINTとinterrupt()でターンを終える
ターンを終わらせてから止めたい場合は、SIGINTを送ります。Agent SDKを使う側ならinterrupt()を呼んでから、プロセスを止めます。
interrupt()はストリーミング入力モードでのみ使えます。v2.1.205以降のCLIでは、中断時点で待機中だったユーザーメッセージのUUID一覧が受領情報として返ります。待機中のメッセージは、キャンセルを指示しない限り中断の後に処理されます。
シェルスクリプトから止める側の例です。まずSIGINTで今のターンを終わらせ、一定時間待ってから、残っていればSIGTERMに切り替えます。
claude -p "Run the test suite and fix failures" \
--permission-mode acceptEdits &
pid=$!
# 上限時間に達したら、まずSIGINTでターンを終わらせる
sleep 600
kill -INT "$pid" 2>/dev/null || exit 0
# 猶予のあとも残っていればSIGTERMで止める(終了コード143)
sleep 60
kill -TERM "$pid" 2>/dev/null
wait "$pid"
echo "exit: $?"待ち時間の600秒と60秒は例です。SIGINTでターンが終わるまでの時間はターンの内容で変わるため、自分のジョブで実測して決めます。
止めたあとの再開
止めたセッションは--continueや--resumeで再開できます。中断したターンは、再開時にそのまま残ります。次のプロンプトが会話を進める形になり、中断したターンの続きを自動でやり直したりはしません。
続きから走らせたい場合は、環境変数CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1を設定します。前回のセッションがターンの途中で終わっていたときに、モデルがプロンプトを再送されなくても続けます。
CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1 \
claude -p "続きをお願いします" --resume "$session_id"続きの指示の文面はCLAUDE_CODE_RESUME_PROMPTで変えられ、既定はContinue from where you left off.です。古い記録を再実行したくないときは、CLAUDE_CODE_RESUME_INTERRUPTED_TURN_MAX_AGE_MSで最後のメッセージの古さに上限を付けます(v2.1.211以降)。上限を超えると自動再開はされず、セッションは待機状態で始まります。
--resumeに渡すセッションIDは、--output-format jsonの出力にあるsession_idから取れます。止めた側に出力が残っていないときは、SessionEndフックの入力に含まれるsession_idをファイルへ書き出しておくと、あとから再開先を特定できます。transcript_pathも同じ入力にあるので、中断時点の会話記録を確かめたいときに使えます。GitHub Actionsでの組み込みはClaude CodeをGitHub Actionsに組み込むにあります。
よくある疑問への答え
143はエラーとして扱うべきか
143は「止められた」ことを表す値で、claude -pが処理に失敗した印ではありません。ジョブが自分で止めた場合(タイムアウト)と、外から止められた場合(スケジューラの強制停止など)は、呼び出し側で区別しておくと原因を追いやすくなります。
シェルスクリプトなら、waitが返す終了コードで分岐できます。143を「SIGTERMで止まった」として記録し、それ以外の非0と分けておきます。
wait "$pid"
code=$?
if [ "$code" -eq 143 ]; then
echo "SIGTERMで停止: セッションを--resumeで再開できます"
elif [ "$code" -ne 0 ]; then
echo "失敗: exit $code"
fi143を見たジョブは、--output-format jsonで控えておいたsession_idを使って、同じセッションを--resumeで再開する流れに繋げられます。
バックグラウンド処理の待機中に止めたら
claude -pはターンを終えた後も、バックグラウンドのコマンドやサブエージェントの完了を待つことがあります。待ち自体は10分のアイドルで打ち切られる既定があり、CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MSで変えられます。この待機中にSIGTERMを受けたときの個別の挙動は、記載がありません。止めたい理由が「待ちが長い」なら、シグナルより先にこの上限を調整できます。
固まって出力が出ないときも、同じ止め方でよいか
止め方は同じですが、原因は別に調べます。macOSのlaunchdから起動して固まる例はlaunchdで固まるときの原因と対処法、認証切れが原因の例はOAuth session expiredで失敗する原因と回避策に書いています。