🆕

Claude Code Hooksを簡単に作れるPythonフレームワーク「claude-pyhooks」を作った

に公開

はじめに

最近Claude Codeに新機能Hooksが出ましたね!
そこで、Hooksをpythonで気軽に書けるフレームワークを作りました。

「シェルスクリプトよりpythonスクリプトで書きたい」、「入出力のjsonをいちいち調べたくない」といった思いがあり、開発に踏み切った次第です。
Hooksと実例を確認しながら開発しましたが、今後ClaudeのHooksのAPIに変更があったりそもそも私が勘違いしていたりするかもしれませんので、おかしなところがあればご連絡いただけると助かります。

claude-pyhooksとは

claude-pyhooksは、Claude Code hooks開発を効率化するPythonライブラリです。面倒な下回りの処理を隠蔽しているつもりです。

GitHub : claude-pyhooks
こちらのREADME(or README-ja)やtests,examplesもご覧ください。

基本的な使い方

インストール

pip install claude-pyhooks # とか
uv add claude-pyhooks # とか

Claude Code設定

~/.claude/settings.jsonなど好きなところに設定を追加しましょう。もちろん、Claude Codeの/hooksから設定してもいいです。(uvを使う場合の)設定例は以下の通り。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "uv run /path/to/pre_bash.py"
          }
        ]
      }
    ]
  }
}

対応するhookタイプ

  • PreToolUse: ツール実行前の制御(危険コマンドブロック、確認など)
  • PostToolUse: ツール実行後の処理(ログ、バックアップ、音声通知など)
  • Notification: Claude Codeからの通知処理
  • Stop: セッション終了時の処理
  • SubagentStop: サブエージェント終了時の処理

以下では、かなりシンプルな(本ライブラリを使うまでもないかもしれない)例を列挙します。

1. 危険コマンドをブロックする

#!/usr/bin/env python3
"""危険なbashコマンドを実行前にブロックする"""

from claude_pyhooks import BashInput, PreToolUseInput, PreToolUseOutput

DANGEROUS_COMMANDS = [
    "rm -rf /",
    "sudo rm",
    "mkfs",
    "format",
    "dd if=/dev/zero",
]

# Claude Codeからの入力を読み取り
hook_input = PreToolUseInput.from_stdin()

if hook_input.tool_name == "Bash":
    command = hook_input.tool_input.command
    
    # 危険コマンドのチェック
    for dangerous in DANGEROUS_COMMANDS:
        if dangerous in command:
            PreToolUseOutput.block(
                f"危険なコマンドがブロックされました: {dangerous}"
            ).execute()
  • PreToolUseInputのdataclassにはtool_inputというfieldがあり、これはtool_nameを読み取って自動で対応するInput(上の例ではBashInput)に変換されます。
  • 本ライブラリの実行可能関数(といっても現状はOutputとBeepだけ)はすべてCommandになっており、execute()することで実行されます。Command用のQueueCommandQueueも用意してあります。
  • PreToolUseOutputはいくつかのフィールドを持つdataclassですが、実用的にはblockしたいときにクラス関数のblock(reason)で十分だと思います。blockするようなoutputの場合、executeした段階でエラーコード2が渡されて終了します。

2. 実行結果をログに残す

#!/usr/bin/env python3
"""bashコマンドの実行結果をログに記録"""

from pathlib import Path
from claude_pyhooks import BashInput, BashResponse, PostToolUseInput

hook_input = PostToolUseInput.from_stdin()

if hook_input.tool_name == "Bash":
    command = hook_input.tool_input.command
    success = not hook_input.tool_response.stderr
    
    status = "成功" if success else "失敗"
    log_path = Path("/tmp/claude_log.log").expanduser()
    
    with open(log_path, "a", encoding="utf-8") as f:
        f.write(f"[{status}] {command}\n")
  • PostToolUseInputの使い勝手はPreToolUseInputとほぼ同じです。
    • tool_responseが追加されており、これも自動で対応するResponseクラスに変換されます。
  • PostToolUseInputPreToolUseOutputとほぼ同じ。

3. 通知に音で反応する

#!/usr/bin/env python3
"""通知内容に応じて音を鳴らす"""

from claude_pyhooks import (
    DEFAULT_BEEP, ERROR_BEEP, SUCCESS_BEEP, WARNING_BEEP,
    NotificationInput
)

hook_input = NotificationInput.from_stdin()

# デフォルトはシンプルなビープ音
beep = DEFAULT_BEEP

if hook_input.message:
    message = hook_input.message.lower()
    
    if "error" in message or "fail" in message:
        beep = ERROR_BEEP
    elif "success" in message or "complete" in message:
        beep = SUCCESS_BEEP
    elif "warning" in message:
        beep = WARNING_BEEP

beep.execute()
  • 適当に4種類BEEPをデフォルトで作ってみましたが、もちろんカスタマイズ可能。
  • BeepCommand,BeepSequence,SleepCommandを組み合わせれば好きなbeepが作れます。

まとめ

hooksがあるのはいいけど、凝ったことをしようとすると意外と面倒だ、と思った人は少なくないのではないでしょうか。私もその一人で、こういう便利ツールを作ってみました。(結果、ad hocに作るよりも100倍時間がかかりました)

もちろんこのプロジェクトにClaude Codeの手をたくさん借りましたが、完全にお任せ、というわけにはいきませんでしたね。特に設計や型周りをこだわりがあると、結局自分で書き直さざるを得なくなった部分が多々出てきました。うまくCLAUDE.mdとかを使えばこれも改善されるのでしょうか......

欲しい機能やバグなどございましたら、連絡いただければ頑張って対応します! また、特急で作ったためドキュメントが整備されておりませんが、そちらも少しずつ整備していくつもりです。

皆さんのClaude Code体験がより良いものになれば幸いです。

Discussion