UserPromptSubmitの使い方
UserPromptSubmit hookは既定で30秒で打ち切られます。文脈を足すhookと、APIキーを止めるhookを載せます。
- UserPromptSubmit は、プロンプトを送ったとき Claude が処理する前に動く hook で、文脈を足したりプロンプトを止めたりできます。
- 自分で打ったときだけでなく、予約タスクやバックグラウンドのサブエージェントの結果、別のセッションからのメッセージでも動きます。
- decision: "block" で止めても、送った文は手元のセッションの記録に残ることがあります。防げるのは鍵がモデルに届くことです。
UserPromptSubmit は、プロンプトを送ったとき、Claude が処理する前に動く hook です。プロンプトに文脈を足したり、プロンプトそのものを Claude に届く前に止めたりできます。この記事では、ダミーの todo アプリを例に、今のブランチと日付を足す hook と、API キーらしき文字列を含むプロンプトを止める hook の2つを載せます。編集部の検証環境(macOS、Claude Code 2.1.309、jq 1.7.1、2026年10月23日)で、スクリプトにダミーの JSON を流して確かめました。実際のセッションの中で発火するところは見ていません(未実行)。hook を使う考え方はClaude Code hooks実践にまとめています。
いつ動き、何が渡されるか
UserPromptSubmit は、自分で打ったプロンプトのときだけ動くわけではありません。次のときにも動きます。
- セッションの中の予約タスクが動いたとき(
/loopの繰り返しも含む) - バックグラウンドのサブエージェントが、元のセッションに結果を返したとき
- 別のセッションから、メインの会話にメッセージが届いたとき
何かを止める hook を書く前に、これを頭に入れておきます。
入力の JSON には、共通のフィールドに加えて、送った文が prompt として入ります。貼り付けた文が [Pasted text #N] にまとめられていても、hook には展開された中身が届きます。セッションに独自の名前が付いていれば session_title も入ります。/deploy staging のように skill やコマンドを打った場合、hook が受け取るのは打ったとおりの1行で、展開された skill の中身ではありません。
{
"hook_event_name": "UserPromptSubmit",
"prompt": "todo に期限を付けたい"
}
出典:https://code.claude.com/docs/en/hooks 、2026-10-23確認
例1:プロンプトに文脈を足す
exit 0 で終わると、stdout に出した普通の文が Claude の文脈に足されます。JSON の additionalContext でも同じです。どちらも会話の画面には出ず、hook の名前で始まる system reminder として Claude に届きます。
#!/usr/bin/env bash
# .claude/hooks/prompt-context.sh (UserPromptSubmit)
cd "${CLAUDE_PROJECT_DIR:?}" || exit 0
branch=$(git branch --show-current 2>/dev/null)
changed=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
echo "Current branch: ${branch:-unknown}. Uncommitted files: ${changed}. Today: $(date +%F)."
exit 0
feature/due-dates のブランチにしたダミーのリポジトリで流した結果です。
$ echo '{"hook_event_name":"UserPromptSubmit","prompt":"todo に期限を付けたい"}' | .claude/hooks/prompt-context.sh
Current branch: feature/due-dates. Uncommitted files: 4. Today: 2026-10-23.
exit=0
time で3回測ると、0.03〜0.04 秒でした。この1行は、セッションの間ずっと、プロンプトのたびに足されます。Claude が自分では調べられず、たいていのターンで要るものだけを足します。何の役にも立たない1行でも、毎回コンテキストを使います。
例2:API キーらしき文字列を止める
止めるときは、decision: "block" の JSON を出します。reason は人に見せる文で、Claude の文脈には入りません。
#!/usr/bin/env bash
# .claude/hooks/block-secrets-in-prompt.sh (UserPromptSubmit)
prompt=$(jq -r '.prompt // ""')
if printf '%s' "$prompt" | grep -Eq 'sk-ant-[A-Za-z0-9_-]{10,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{30,}'; then
jq -n '{decision: "block",
reason: "プロンプトに API キーらしき文字列があります。消してから、環境変数で渡してください。",
hookSpecificOutput: {hookEventName: "UserPromptSubmit", suppressOriginalPrompt: true}}'
fi
exit 0
ダミーの鍵(sk-ant-api03-DUMMY...)を入れたプロンプトを流した結果です。
{
"decision": "block",
"reason": "プロンプトに API キーらしき文字列があります。消してから、環境変数で渡してください。",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"suppressOriginalPrompt": true
}
}
exit=0
「todo に期限を付けたい」では、何も出さずに exit 0 でした。
既定では、止めたときのメッセージの最後に Original prompt: と元の文が付きます。hookSpecificOutput の中で suppressOriginalPrompt: true にすると、これを省けます。
これで鍵がディスクに残らなくなるわけではありません。 公式によると、suppressOriginalPrompt が変えるのは止めたときのメッセージだけで、送った文はセッションの記録やプロンプトの履歴といった手元のファイルに残ることがあります。hook が防ぐのは、鍵がモデルに届くことです。貼ってしまった鍵は作り直します。考え方はClaude Codeに秘密を渡さないにあります。
出典:https://code.claude.com/docs/en/hooks 、2026-10-23確認
返し方の一覧
| hook の出力 | 効果 |
|---|---|
exit 0、普通の文を stdout に出す | その文が Claude の文脈に足される |
exit 0、JSON の additionalContext | その文字列が Claude の文脈に足される |
JSON の decision: "block" と reason | プロンプトが Claude に届かない。reason は人にだけ見える |
hookSpecificOutput.suppressOriginalPrompt: true | 止めたときのメッセージから元の文を省く |
hookSpecificOutput.sessionTitle | セッションの名前を付ける |
exit 2 と stderr | プロンプトを止める。stderr は人に見せ、文脈には入らない |
出典:https://code.claude.com/docs/en/hooks 、2026-10-23確認
settings.json と注意点
UserPromptSubmit は matcher に対応していないので、matcher は書きません。
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prompt-context.sh" },
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-secrets-in-prompt.sh" }
]
}
]
}
}
この設定は jq を通り、claude doctor でも不正な設定は出ませんでした。
- タイムアウト:command・HTTP・MCP ツールの hook の既定は30秒で、ほかの多くのイベントの600秒より短い。hook が終わるまでモデルの処理が待たされるので、止まった hook はセッションを止める
- タイムアウトしたとき:出力は捨てられ、
additionalContextも届かないまま、プロンプトは Claude に届く。止めたいなら"onFailure": "block"を付ける - 特定の skill やコマンドだけに反応させたいなら、
UserPromptExpansionのイベントを使う
出典:https://code.claude.com/docs/en/hooks 、2026-10-23確認
ネットワークに出る処理やテストの実行は、ここに入れません。編集のあとにテストを回すなら PostToolUse が向いています。書き方はPostToolUseで整形とテストを自動化にあります。hook が動かないときの確かめ方はhooksが動かないときの確認にまとめています。
まとめ
- UserPromptSubmit は、予約タスク、サブエージェントの報告、別のセッションからのメッセージも含めて、プロンプトのたびに動く
- 既定のタイムアウトは30秒。タイムアウトすると足した文脈は捨てられるので、速く終わる処理だけにする
- exit 0 で stdout に出した文が文脈になる。たいていのターンで要るものだけを足す
- 止めるときは
decision: "block"。reasonは人に届き、Claude には届かない - 止めたプロンプトも手元のファイルに残ることがある。貼ってしまった鍵は作り直す