factsGemini CLIhooks

Gemini CLI hooks

概要

agentic loop の特定の地点で Gemini CLI が実行するスクリプトまたはプログラム。CLI のソースを変えずに挙動を横取り・カスタマイズできる。hook は agent loop の一部として同期的に走り、イベント発火時は一致するすべての hook の完了を待ってから継続する。

仕様

イベント

イベント発火タイミング影響
SessionStartセッション開始時(startup / resume / clear)コンテキスト注入
SessionEndセッション終了時(exit / clear)Advisory
BeforeAgentユーザーがプロンプトを送信した後、計画の前ターンのブロック/コンテキスト
AfterAgentagent loop の終了時リトライ/停止
BeforeModelLLM へのリクエスト送信前ターンのブロック/モック
AfterModelLLM 応答の受信後ターンのブロック/redact
BeforeToolSelectionLLM がツールを選ぶ前ツールのフィルタ
BeforeToolツール実行前ツールのブロック/書き換え
AfterToolツール実行後結果のブロック/コンテキスト
PreCompressコンテキスト圧縮の前Advisory
Notificationシステム通知が発生したときAdvisory

入出力の規約(Golden Rule)

hook は stdin(入力)と stdout(出力)で通信する。

  1. 出力は最終的な JSON オブジェクト以外を stdout に出してはならない。 JSON の前に echoprint が 1 回でもあるとパースが壊れる
  2. stdout に非 JSON テキストが混ざるとパースに失敗し、CLI は「Allow」に既定し、出力全体を systemMessage として扱う
  3. ログとデバッグはすべて stderr に出す。Gemini CLI は stderr を捕捉するが、JSON としてパースしようとはしない

終了コード

コードラベル挙動
0Successstdout を JSON としてパースする。意図的なブロック({"decision": "deny"})を含め、すべてのロジックで推奨されるコード
2System Block対象のアクション(ツール/ターン/停止)を中断する。stderr を拒否理由として使う。高い重大度。セキュリティ停止やスクリプト失敗に使う
その他Warning致命的でない失敗。警告を出すが、元のパラメータで処理を続行する

matcher

  • ツールイベント(BeforeToolAfterTool): 正規表現"write_.*" など)
  • ライフサイクルイベント: 完全一致の文字列"startup" など)
  • ワイルドカード: "*" または ""(空文字列)ですべてに一致
  • MCP のツール名は mcp_<server_name>_<tool_name> の形

設定

settings.jsonhooks オブジェクトで定義する。複数レイヤーがマージされ、優先度は高い順に:

  1. Project settings: 現在のディレクトリの .gemini/settings.json
  2. User settings: ~/.gemini/settings.json
  3. System settings: /etc/gemini-cli/settings.json
  4. Extensions: インストール済み extension が定義する hook

hook definition(イベント配下の各要素):

フィールド必須説明
matcherstringいいえツールでは正規表現、ライフサイクルでは完全一致文字列
sequentialbooleanいいえtrue でグループ内の hook を順に実行する。false なら並列
hooksarrayはいhook configuration の配列

hook configuration:

フィールド必須説明
typestringはい実行エンジン。現時点では "command" のみサポート
commandstringはいtype"command" のとき)実行するシェルコマンド
namestringいいえログや CLI コマンドで識別するための名前
timeoutnumberいいえミリ秒単位のタイムアウト。既定 60000
descriptionstringいいえ目的の簡単な説明

環境変数

hook はサニタイズされた環境で実行される。

変数内容
GEMINI_PROJECT_DIRプロジェクトルートの絶対パス
GEMINI_PLANS_DIRplans ディレクトリの絶対パス
GEMINI_SESSION_ID現在のセッションの一意な ID
GEMINI_CWD現在の作業ディレクトリ
CLAUDE_PROJECT_DIR互換のためのエイリアス

共通の入力フィールド

すべての hook が stdin で受け取る。

フィールド内容
session_idstring現在のセッションの一意な ID
transcript_pathstringセッションのトランスクリプト JSON への絶対パス
cwdstring現在の作業ディレクトリ
hook_event_namestring発火したイベント
timestampstringISO 8601 の実行時刻

共通の出力フィールド

フィールド内容
systemMessagestringターミナルに即座に表示される
suppressOutputbooleantrue で hook の内部メタデータをログ/テレメトリから隠す
continuebooleanfalse で agent loop 全体を即座に止める
stopReasonstringcontinuefalse のときユーザーに表示される
decisionstring"allow" または "deny"(別名 "block")。具体的な影響はイベントによる
reasonstringdecision"deny" のときのフィードバック/エラーメッセージ

イベント別の仕様

BeforeTool

  • 入力: tool_nametool_input(モデルが生成した生の引数)、mcp_context(MCP ツールの任意メタデータ)、original_request_nametail tool call の場合の元のツール名
  • 出力
    • decision: "deny"(または "block")でツールの実行を防ぐ
    • reason(deny 時は必須)はツールエラーとしてエージェントに送られ、エージェントは応答や再試行ができる
    • hookSpecificOutput.tool_input: 実行前にモデルの引数とマージして上書きする
    • continue: false で agent loop 全体を即座に終わらせる
  • exit code 2: 実行を防ぐ。stderr をエージェントへの reason として使う。ターンは継続する

AfterTool

  • 入力: tool_nametool_input(元の引数)、tool_responsellmContentreturnDisplay、任意の error)、mcp_contextoriginal_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 回、モデルが最終応答を生成した後に発火する
  • 入力: promptprompt_response(エージェントが生成した最終テキスト)、stop_hook_activeこの hook が既にリトライ列の一部として動いているか
  • 出力
    • decision: "deny" で応答を拒否しリトライを強制する
    • reason(deny 時は必須)は新しいプロンプトとしてエージェントに送られ、修正を要求する
    • continue: false でリトライせずセッションを止める
    • hookSpecificOutput.clearContext: true で、UI 表示は保ったまま会話履歴(LLM のメモリ)をクリアする
  • exit code 2: 応答を拒否し、stderr をフィードバックプロンプトとして自動リトライのターンを起こす

BeforeModel

  • SDK 非依存の安定したリクエスト形式で動く
  • 入力: llm_requestmodelmessagesconfig
  • 出力
    • hookSpecificOutput.llm_request: 送信リクエストの一部を上書きする(モデルや temperature の変更など)
    • hookSpecificOutput.llm_response: Synthetic Response。これを返すと CLI は LLM 呼び出しを完全に飛ばし、これを応答として使う
    • decision: "deny" でリクエストをブロックしターンを中断する
  • exit code 2: ターンを中断し LLM 呼び出しを飛ばす。stderr をエラーメッセージとして使う

BeforeToolSelection

  • 入力: llm_requestBeforeModel と同形式)
  • 出力
    • hookSpecificOutput.toolConfig.mode: "AUTO" / "ANY" / "NONE""NONE" は全ツールを無効にし、他の hook より優先する"ANY" は少なくとも 1 回のツール呼び出しを強制する
    • hookSpecificOutput.toolConfig.allowedFunctionNames: ツール名のホワイトリスト
  • Union 戦略: 複数 hook のホワイトリストは結合される
  • 制限: decisioncontinuesystemMessageサポートしない

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 のみ。continuedecision は無視され、起動はブロックされない

SessionEnd

  • 入力: reason"exit" / "clear" / "logout" / "prompt_input_exit" / "other"
  • 出力: systemMessage(シャットダウン中に表示)
  • best effort。CLI はこの hook の完了を待たず、フロー制御フィールド(continuedecision)をすべて無視する

Notification

  • 入力: notification_type"ToolPermission")、messagedetails
  • 出力: systemMessage
  • observability 専用。アラートをブロックしたり権限を自動付与したりはできない。フロー制御フィールドは無視される

PreCompress

  • 入力: trigger"auto" / "manual"
  • 出力: systemMessage
  • advisory のみ。非同期に発火し、圧縮処理をブロックも変更もできない。フロー制御フィールドは無視される

Stable Model API

SDK 更新で hook が壊れないよう、LLMRequestLLMResponse の構造が定義されている。

  • LLMRequest: modelmessagesrolecontent非テキストのパートは hook 向けに除外される)、configtoolConfig
  • LLMResponse: candidatescontentfinishReason)、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.md
  • facts/gemini-cli/commands.md
  • facts/gemini-cli/tools.md
  • facts/gemini-cli/extensions.md
  • facts/gemini-cli/trusted-folders.md