Gemini CLI hooks
概要
agentic loop の特定の地点で Gemini CLI が実行するスクリプトまたはプログラム。CLI のソースを変えずに挙動を横取り・カスタマイズできる。hook は agent loop の一部として同期的に走り、イベント発火時は一致するすべての hook の完了を待ってから継続する。
仕様
イベント
| イベント | 発火タイミング | 影響 |
|---|---|---|
SessionStart | セッション開始時(startup / resume / clear) | コンテキスト注入 |
SessionEnd | セッション終了時(exit / clear) | Advisory |
BeforeAgent | ユーザーがプロンプトを送信した後、計画の前 | ターンのブロック/コンテキスト |
AfterAgent | agent loop の終了時 | リトライ/停止 |
BeforeModel | LLM へのリクエスト送信前 | ターンのブロック/モック |
AfterModel | LLM 応答の受信後 | ターンのブロック/redact |
BeforeToolSelection | LLM がツールを選ぶ前 | ツールのフィルタ |
BeforeTool | ツール実行前 | ツールのブロック/書き換え |
AfterTool | ツール実行後 | 結果のブロック/コンテキスト |
PreCompress | コンテキスト圧縮の前 | Advisory |
Notification | システム通知が発生したとき | Advisory |
入出力の規約(Golden Rule)
hook は stdin(入力)と stdout(出力)で通信する。
- 出力は最終的な JSON オブジェクト以外を
stdoutに出してはならない。 JSON の前にechoやprintが 1 回でもあるとパースが壊れる stdoutに非 JSON テキストが混ざるとパースに失敗し、CLI は「Allow」に既定し、出力全体をsystemMessageとして扱う- ログとデバッグはすべて
stderrに出す。Gemini CLI はstderrを捕捉するが、JSON としてパースしようとはしない
終了コード
| コード | ラベル | 挙動 |
|---|---|---|
0 | Success | stdout を JSON としてパースする。意図的なブロック({"decision": "deny"})を含め、すべてのロジックで推奨されるコード |
2 | System Block | 対象のアクション(ツール/ターン/停止)を中断する。stderr を拒否理由として使う。高い重大度。セキュリティ停止やスクリプト失敗に使う |
| その他 | Warning | 致命的でない失敗。警告を出すが、元のパラメータで処理を続行する |
matcher
- ツールイベント(
BeforeTool、AfterTool): 正規表現("write_.*"など) - ライフサイクルイベント: 完全一致の文字列(
"startup"など) - ワイルドカード:
"*"または""(空文字列)ですべてに一致 - MCP のツール名は
mcp_<server_name>_<tool_name>の形
設定
settings.json の hooks オブジェクトで定義する。複数レイヤーがマージされ、優先度は高い順に:
- Project settings: 現在のディレクトリの
.gemini/settings.json - User settings:
~/.gemini/settings.json - System settings:
/etc/gemini-cli/settings.json - Extensions: インストール済み extension が定義する hook
hook definition(イベント配下の各要素):
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
matcher | string | いいえ | ツールでは正規表現、ライフサイクルでは完全一致文字列 |
sequential | boolean | いいえ | true でグループ内の hook を順に実行する。false なら並列 |
hooks | array | はい | hook configuration の配列 |
hook configuration:
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
type | string | はい | 実行エンジン。現時点では "command" のみサポート |
command | string | はい(type が "command" のとき) | 実行するシェルコマンド |
name | string | いいえ | ログや CLI コマンドで識別するための名前 |
timeout | number | いいえ | ミリ秒単位のタイムアウト。既定 60000 |
description | string | いいえ | 目的の簡単な説明 |
環境変数
hook はサニタイズされた環境で実行される。
| 変数 | 内容 |
|---|---|
GEMINI_PROJECT_DIR | プロジェクトルートの絶対パス |
GEMINI_PLANS_DIR | plans ディレクトリの絶対パス |
GEMINI_SESSION_ID | 現在のセッションの一意な ID |
GEMINI_CWD | 現在の作業ディレクトリ |
CLAUDE_PROJECT_DIR | 互換のためのエイリアス |
共通の入力フィールド
すべての hook が stdin で受け取る。
| フィールド | 型 | 内容 |
|---|---|---|
session_id | string | 現在のセッションの一意な ID |
transcript_path | string | セッションのトランスクリプト JSON への絶対パス |
cwd | string | 現在の作業ディレクトリ |
hook_event_name | string | 発火したイベント |
timestamp | string | ISO 8601 の実行時刻 |
共通の出力フィールド
| フィールド | 型 | 内容 |
|---|---|---|
systemMessage | string | ターミナルに即座に表示される |
suppressOutput | boolean | true で hook の内部メタデータをログ/テレメトリから隠す |
continue | boolean | false で agent loop 全体を即座に止める |
stopReason | string | continue が false のときユーザーに表示される |
decision | string | "allow" または "deny"(別名 "block")。具体的な影響はイベントによる |
reason | string | decision が "deny" のときのフィードバック/エラーメッセージ |
イベント別の仕様
BeforeTool
- 入力:
tool_name、tool_input(モデルが生成した生の引数)、mcp_context(MCP ツールの任意メタデータ)、original_request_name(tail tool call の場合の元のツール名) - 出力
decision: "deny"(または"block")でツールの実行を防ぐreason(deny 時は必須)はツールエラーとしてエージェントに送られ、エージェントは応答や再試行ができるhookSpecificOutput.tool_input: 実行前にモデルの引数とマージして上書きするcontinue: falseで agent loop 全体を即座に終わらせる
- exit code 2: 実行を防ぐ。
stderrをエージェントへのreasonとして使う。ターンは継続する
AfterTool
- 入力:
tool_name、tool_input(元の引数)、tool_response(llmContent、returnDisplay、任意のerror)、mcp_context、original_request_name - 出力
decision: "deny"で実際のツール出力をエージェントから隠すreason(deny 時は必須)はモデルに返すツール結果を置き換えるhookSpecificOutput.additionalContext: ツール結果に追記されるhookSpecificOutput.tailToolCallRequest({ name, args }): このツールの直後に別のツールを実行する要求。その tail call の結果が元のツールの応答を置き換えるcontinue: falseで agent loop 全体を終わらせる
- exit code 2: ツール結果を隠す。
stderrを置き換え内容としてエージェントに送る。ターンは継続する
BeforeAgent
- 入力:
prompt(ユーザーが送った元のテキスト) - 出力
hookSpecificOutput.additionalContext: そのターンに限りプロンプトへ追記されるdecision: "deny"でターンをブロックし、ユーザーのメッセージを破棄する(履歴に残らない)continue: falseでターンをブロックするが、メッセージは履歴に残すreason(deny または停止時は必須)
- exit code 2: ターンを中断しコンテキストからプロンプトを消す。
decision: "deny"と同じ
AfterAgent
- ターンごとに 1 回、モデルが最終応答を生成した後に発火する
- 入力:
prompt、prompt_response(エージェントが生成した最終テキスト)、stop_hook_active(この hook が既にリトライ列の一部として動いているか) - 出力
decision: "deny"で応答を拒否しリトライを強制するreason(deny 時は必須)は新しいプロンプトとしてエージェントに送られ、修正を要求するcontinue: falseでリトライせずセッションを止めるhookSpecificOutput.clearContext:trueで、UI 表示は保ったまま会話履歴(LLM のメモリ)をクリアする
- exit code 2: 応答を拒否し、
stderrをフィードバックプロンプトとして自動リトライのターンを起こす
BeforeModel
- SDK 非依存の安定したリクエスト形式で動く
- 入力:
llm_request(model、messages、config) - 出力
hookSpecificOutput.llm_request: 送信リクエストの一部を上書きする(モデルや temperature の変更など)hookSpecificOutput.llm_response: Synthetic Response。これを返すと CLI は LLM 呼び出しを完全に飛ばし、これを応答として使うdecision: "deny"でリクエストをブロックしターンを中断する
- exit code 2: ターンを中断し LLM 呼び出しを飛ばす。
stderrをエラーメッセージとして使う
BeforeToolSelection
- 入力:
llm_request(BeforeModelと同形式) - 出力
hookSpecificOutput.toolConfig.mode:"AUTO"/"ANY"/"NONE"。"NONE"は全ツールを無効にし、他の hook より優先する。"ANY"は少なくとも 1 回のツール呼び出しを強制するhookSpecificOutput.toolConfig.allowedFunctionNames: ツール名のホワイトリスト
- Union 戦略: 複数 hook のホワイトリストは結合される
- 制限:
decision、continue、systemMessageをサポートしない
AfterModel
- 入力:
llm_request(元のリクエスト)、llm_response(ストリーミング中は 1 チャンク) - 出力
hookSpecificOutput.llm_response: モデルの応答チャンクを置き換えるdecision: "deny"で応答チャンクを破棄しターンをブロックするcontinue: falseで agent loop 全体を終わらせる
- ストリーミングではモデルが生成する全チャンクに対して発火する。応答の変更は現在のチャンクにのみ影響する
- exit code 2: ターンを中断しモデル出力を破棄する。
stderrをエラーメッセージとして使う
SessionStart
- 入力:
source("startup"/"resume"/"clear") - 出力
hookSpecificOutput.additionalContext: 対話モードでは履歴の最初のターンとして注入され、非対話モードではユーザーのプロンプトの先頭に付くsystemMessage: セッション開始時に表示される
- advisory のみ。
continueとdecisionは無視され、起動はブロックされない
SessionEnd
- 入力:
reason("exit"/"clear"/"logout"/"prompt_input_exit"/"other") - 出力:
systemMessage(シャットダウン中に表示) - best effort。CLI はこの hook の完了を待たず、フロー制御フィールド(
continue、decision)をすべて無視する
Notification
- 入力:
notification_type("ToolPermission")、message、details - 出力:
systemMessage - observability 専用。アラートをブロックしたり権限を自動付与したりはできない。フロー制御フィールドは無視される
PreCompress
- 入力:
trigger("auto"/"manual") - 出力:
systemMessage - advisory のみ。非同期に発火し、圧縮処理をブロックも変更もできない。フロー制御フィールドは無視される
Stable Model API
SDK 更新で hook が壊れないよう、LLMRequest と LLMResponse の構造が定義されている。
LLMRequest:model、messages(roleとcontent。非テキストのパートは hook 向けに除外される)、config、toolConfigLLMResponse:candidates(content、finishReason)、usageMetadata.totalTokenCount
hook の管理コマンド
- 表示:
/hooks panel - 一括:
/hooks enable-all//hooks disable-all - 個別:
/hooks enable <name>//hooks disable <name>
設定
{
"hooks": {
"BeforeTool": [
{
"matcher": "write_file|replace",
"hooks": [
{
"name": "security-check",
"type": "command",
"command": "$GEMINI_PROJECT_DIR/.gemini/hooks/security.sh",
"timeout": 5000
}
]
}
]
}
}
制約・注意点
- hook はユーザー権限で任意のコードを実行する。 hook を設定することは、自分のマシンでスクリプトにシェルコマンドを実行させることを許すのと同じ
- project レベルの hook は、信頼できないプロジェクトを開くときに特に危険
- Gemini CLI は project hook を fingerprint する。hook の名前やコマンドが変わると(
git pullなどで)新しい untrusted な hook として扱われ、実行前に警告が出る
関連
facts/gemini-cli/configuration.mdfacts/gemini-cli/commands.mdfacts/gemini-cli/tools.mdfacts/gemini-cli/extensions.mdfacts/gemini-cli/trusted-folders.md