0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Gemini CLI × tmux】エージェントの状態をウィンドウ名に表示して、確認待ちを見逃さないハック

0
Last updated at Posted at 2026-02-11

はじめに

Gemini CLI、便利に使っていますか?
コーディングをガッツリ任せられるので最高なのですが、処理が長いときに別のtmuxウィンドウで作業していると、「ツールの実行許可待ち(y/n)」で止まっているのに気づかず放置し、Geminiをフルに活かせないことがあります。

2026/1/28にGemini CLIもhooksがリリースされていたので、これを使ってtmuxのウィンドウ名にGeminiの状態を表示するようにGemini CLIで実装をし、動作確認をしました。
Tailor Gemini CLI to your workflow with hooks - Google Developers Blog
Gemini CLI hooks | Gemini CLI

Claude Codeで同じことをしている方がおり、pythonスクリプトはそちらのコードを大部分参考にさせていただきましたm(_ _)m
Claude Code Hooksでtmuxのウィンドウ名を変更して通知の代わりにする #ClaudeCode - Qiita

何ができるの?

Gemini CLIのHooks機能を利用して、イベント発生時にtmuxのウィンドウ名の先頭にアイコンを付与・更新します。
スクリーンショット 2026-02-11 16.32.29.png
上記の図のように、Geminiの状態が表示されるようになります。

  • 処理中: 💎🤖 window_name
  • 確認待ち: 💎✋ window_name (←これ重要!)
  • 完了: 💎✅ window_name
  • セッション終了: window_name (アイコンを削除)

これで、ステータスラインを見るだけで「お、呼ばれてるな」と気づけるようになります。
ダイヤアイコンなのは、GeminiのGem機能からイメージ合っているなと思いました。

実行環境

項目 バージョン 備考
macOS 26.2 Build 25C56
zsh 5.9 arm-apple-darwin24.2.0
tmux 3.6a
gemini-cli 0.27.4
Python 3.14.3

設定手順

1. スクリプトの準備

まずは、tmuxを操作するためのPythonスクリプトを作成します。

まずディレクトリを作成します。

mkdir -p ~/.gemini/hooks

以下のpythonスクリプトを ~/.gemini/hooks/gemini-hook.py として保存します。

gemini-hook.py
#!/usr/bin/env python3
import json
import os
import re
import subprocess
import sys
from enum import Enum

class HookStatus(Enum):
    COMPLETED = ""
    NOTIFICATION = ""
    ONGOING = "🤖"

    @classmethod
    def get_emoji_pattern(cls) -> str:
        # 置換用に絵文字のパターンを生成
        return "".join(status.value for status in cls) + "💎"

IDENTIFIER = "💎"

def main():
    # 引数からイベント名を取得
    if len(sys.argv) < 2:
        return
    
    event_name = sys.argv[1]

    # 標準入力からJSON(通知内容など)を読み込む
    data = {}
    try:
        if not sys.stdin.isatty():
             data = json.load(sys.stdin)
    except Exception:
        pass

    # イベントに応じてステータスを変更
    if event_name == "notification":
        if data.get("notification_type") == "ToolPermission":
            update_tmux_window_name(HookStatus.NOTIFICATION)
    elif event_name in ["after_agent"]:
        update_tmux_window_name(HookStatus.COMPLETED)
    elif event_name in ["before_agent", "before_tool"]:
        update_tmux_window_name(HookStatus.ONGOING)
    elif event_name in ["after_tool"]:
        update_tmux_window_name(HookStatus.ONGOING)
    elif event_name in ["session_end"]:
        remove_tmux_window_icon()

def get_current_tty() -> str:
    """現在のプロセスの制御端末(TTY)のパスを取得"""
    try:
        # os.ttyname(0)などは /dev/tty を返すことがあり、tmuxの #{pane_tty} (/dev/ttysXXX) と一致しないため
        # psコマンドを使用して具体的なTTYデバイス名を取得する
        result = subprocess.run(
            ["ps", "-p", str(os.getpid()), "-o", "tty="],
            capture_output=True,
            text=True,
            check=True,
        )
        tty_name = result.stdout.strip()

        if not tty_name or tty_name in ["?", "??"]:
            return ""

        if not tty_name.startswith("/"):
            return f"/dev/{tty_name}"
        return tty_name
    except Exception:
        return ""

def is_valid_tmux_context(pane_id: str) -> bool:
    """
    現在のプロセスが指定されたtmuxペイン内で実行されているか確認
    環境変数が継承されただけの別ターミナル(VSCode等)での誤動作を防ぐ
    """
    try:
        current_tty = get_current_tty()
        if not current_tty:
            return False

        # tmuxペインのTTYを取得
        result = subprocess.run(
            ["tmux", "display-message", "-p", "-t", pane_id, "#{pane_tty}"],
            capture_output=True,
            text=True,
            check=True,
        )
        pane_tty = result.stdout.strip()

        return current_tty == pane_tty
    except Exception:
        return False

def update_tmux_window_name(status: HookStatus):
    """指定されたステータスでtmuxウィンドウ名を更新"""
    try:
        # $TMUX_PANE環境変数から実行元のペインIDを取得
        pane_id = os.environ.get("TMUX_PANE")
        if not pane_id:
            return

        # 環境変数が継承されただけの別ターミナル(VSCode等)での誤動作を防ぐ
        if not is_valid_tmux_context(pane_id):
            return

        # ペインが属するウィンドウIDを取得
        result = subprocess.run(
            ["tmux", "display-message", "-p", "-t", pane_id, "#I"],
            capture_output=True,
            text=True,
            check=True,
        )
        window_id = result.stdout.strip()

        # 現在のウィンドウ名を取得
        result = subprocess.run(
            ["tmux", "display-message", "-p", "-t", window_id, "#W"],
            capture_output=True,
            text=True,
            check=True,
        )
        current_name = result.stdout.strip()

        emoji = f"{IDENTIFIER}{status.value}"
        # 既存の絵文字があれば置換、なければ追加
        emoji_pattern = HookStatus.get_emoji_pattern()
        new_name = re.sub(rf"^[{emoji_pattern}]*", f"{emoji}", current_name)
        if not new_name.startswith(emoji):
            new_name = f"{emoji}{current_name}"

        # ウィンドウ名を更新
        subprocess.run(["tmux", "rename-window", "-t", window_id, new_name], check=True)
    except Exception:
        pass

def remove_tmux_window_icon():
    """tmuxウィンドウ名から状態アイコンを削除"""
    try:
        pane_id = os.environ.get("TMUX_PANE")
        if not pane_id:
            return

        # 環境変数が継承されただけの別ターミナル(VSCode等)での誤動作を防ぐ
        if not is_valid_tmux_context(pane_id):
            return

        result = subprocess.run(
            ["tmux", "display-message", "-p", "-t", pane_id, "#I"],
            capture_output=True,
            text=True,
            check=True,
        )
        window_id = result.stdout.strip()

        result = subprocess.run(
            ["tmux", "display-message", "-p", "-t", window_id, "#W"],
            capture_output=True,
            text=True,
            check=True,
        )
        current_name = result.stdout.strip()

        # 先頭の絵文字パターンを削除
        emoji_pattern = HookStatus.get_emoji_pattern()
        new_name = re.sub(rf"^[{emoji_pattern}]+", "", current_name)

        if new_name != current_name:
            subprocess.run(
                ["tmux", "rename-window", "-t", window_id, new_name],
                check=True
            )
    except Exception:
        pass

if __name__ == "__main__":
    main()

2. Gemini CLIの設定

次に~/.gemini/settings.json を開き、hooks セクションに以下の設定を追加します。
イベント発生時に上記のスクリプトを呼び出すようにします。

settings.json
{
   ... 他の設定 ...
   
  "hooks": {
    "Notification": [
      {
        "matcher": ".*",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.gemini/hooks/gemini-hook.py notification"
          }
        ]
      }
    ],
    "AfterAgent": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.gemini/hooks/gemini-hook.py after_agent"
          }
        ]
      }
    ],
    "BeforeAgent": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.gemini/hooks/gemini-hook.py before_agent"
          }
        ]
      }
    ],
    "BeforeTool": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.gemini/hooks/gemini-hook.py before_tool"
          }
        ]
      }
    ],
    "SessionEnd": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.gemini/hooks/gemini-hook.py session_end"
          }
        ]
      }
    ]
  },

  ... 他の設定 ...
}

注意点

  • automatic-rename設定: tmuxの automatic-rename がONの場合、ウィンドウ名が自動リセットされることがあります。
    • 必要に応じて set-option -g automatic-rename off で無効化してください。
  • tmux環境外での実行: スクリプト内で TMUX_PANE の有無をチェックしているので、tmuxの外でGeminiを使ってもエラーにはなりませんが、当然ウィンドウ名は変わりません。
  • Pythonパス: 環境に合わせて python3 のパスや実行権限を調整してください。

まとめ

これで、Geminiが「ツール実行していい?」と聞いてきたときに、ウィンドウ名が 💎✋ に変わるようになりました。
チラッと見るだけで状態がわかるので、開発体験がグッと向上しました!

良きGeminiライフを!

Gemini CLIのHooksを使って通知を出してみたので、よかったらそちらもどうぞ!
Gemini CLIの承認待ちや応答完了の通知をHooksを使って実装してみた #Mac - Qiita

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?