🪕

Claude Code Stop Hooks での自動ワークフローを手軽に構築する

に公開

Hooks を使ったワークフロー構築の仕組み自体については以下を参照してください:
https://zenn.dev/azumag/articles/00b36e074ac220

今回は 状態遷移用の JSON を書いて hooks にスクリプトを指定する のみで、stop hooks ワークフローを手軽に構築する方法を考えます。

単純な逐次実行ならば hooks で && を使うか "order": 1 など順番を記載して実現できる(はず)ですが、本稿の方法だと、claude にプロンプトを実行させること自体を hooks に組み込むことができ、自由度が高いです。

  • タスクの最後に -> "git diff を確認して、README.md に反映せよ。"
  • 終わったら -> "Subagent にレビューを依頼し、それに従って修正"
  • 終わったら -> "commitせよ。変更内容を適切にまとめ、git notes に記載せよ"
    が手軽に構築できます。

構造

  1. Stop Hooks を単一のスクリプトが受け取る
  2. スクリプトは会話ログを取得
  3. 会話ログにおいて、エージェントの最後の発言が特定のフレーズだった場合、その特定のフレーズに応じてタスクを実行する。
  4. Hook の実行がうまくいけば次のフレーズを表示するように Claude に指示する
  5. (3) に戻る

実行可能な2タイプのタスク

設定により2種類の振る舞いをします:
a) {"desicion": "block", "reason":"<指示内容>"} を出力し、任意の指示内容を Claude に実行させる。
b) ユーザ定義のスクリプトを実行し、正常終了なら何も出力せず終了。エラー終了ならスクリプトの stderr を取得して {"desicion": "block", "reason":"<stderr>"} に出力し直し、Claude に対応させる

(a) は手軽にプロンプトを利用し、"コミットして"などの簡単な指示を stop 時に与えるのに便利です。(b) は test 実行など、具体的なスクリプトを実行させて、失敗時に Claude に直させたい場合に便利です。

設定手順

workflow.sh

以下のリポジトリから、クローンするなりコピペするなり、workflow.sh を手に入れます。
https://github.com/azumag/cc-hooks-workflow
Stop hooks ワークフローをハンドリングするスクリプトです。
任意のどこでもよい場所において、以下のように hooks を設定します。
実行権限をつけるのを忘れないように。

.claude/setting.local.json(一例)
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/cc-hooks-workflow/workflow.sh",
            "timeout": 300
          }
        ]
      }
    ]
  }
}

実行が長くなりそうな指示を書きたい時はタイムアウトを長めに設定します。

設定JSON例

状態遷移と実行コマンドを設定する JSON workflow.json を、claude を起動するディレクトリ以下の ./claude/ 直下に用意します。パスが気に入らない場合は workflow.sh を書き換えてください。

workflow.json(一例)
{
  "hooks": [
    {
      "comment": "テスト実行スクリプトを実行",
      "launch": null,
      "path": "/path/to/workdir/.claude/hooks/test.sh",
      "next": "WORKFLOW:TESTED",
      "handling": "block"
    },
    {
      "comment": "テストが終わったらSubAgentに自己レビューと改善を claude に依頼",
      "launch": "WORKFLOW:TESTED",
      "prompt": "## Most Important: That there are no issues, output a single line that says only WORKFLOW:REVIEWED — no other information should be included. ## Review: Assign the task of a strict review to the Review SubAgent, and make necessary corrections based on the results. ## Work Report: $WORK_SUMMARY"
    },
    {
      "comment": "レビューが終わったら CLAUDE.md を再確認させ、重要事項を思い出させる",
      "launch": "WORKFLOW:REVIEWED",
      "prompt": "CLAUDE.md を再確認せよ。再確認後、 WORKFLOW:CLAUDE_RECALL とだけ表示せよ。"
    },
    {
      "launch": "WORKFLOW:CLAUDE_RECALL",
      "path": "/path/to/workflow.sh",
      "args": ["--stop"]
    }
  ]
}

以下に json 構造を解説します。

起動条件

各タスクの起動条件を launch で指定します。これは、直前の会話ログでエージェントが発言した内容 状態フレーズ がこの文字列と合致していたら起動する、という設定ができます。 launchnull を指定した場合はデフォルトの起点タスクとなります。

Promptタスク

json の中で prompt で指定した文字列は、stop hooks の block 理由として claude に渡されます。claude はこれを指示として受け取り、実行します。このとき、必ず次の状態遷移に移るためのフレーズを、完了条件とともに明示しておく必要があります そうでないと起点に戻り続け、無限ループとなります。

pathタスク

起動するスクリプトの path を絶対パスで指定します。workflow.sh はこのパスに指定されたスクリプトを実行し、正常終了なら何も出力せず終了。エラー終了ならスクリプトの stderr を取得して {"desicion": "block", "reason":"<stderr>"} に出力し直し、Claude に対応させます。
args 指定でスクリプトに引数を渡すことができます。
next で次の状態フレーズを指定します。

終了指定

workflow.sh 自体を最後の状態での起動スクリプトに指定し、--stop を args 指定で与えると、即座に exit 0 を返して、ワークフローの終了タスクとして振舞います。これ以外にも、pathタイプのタスクなら、next を指定しなければ exit 0 を返して終わりにするため、終了タスクとして振舞います。

終わりに

ちょっとした hook のフローを設定したい場合は、json と sh だけで段階的タスクを手軽に設定できるので、個人的には重宝しています。とくに、自己レビューを挟む、CIを監視してもらう、などを取り入れると、「できました!」→確認してみるとできてない のストレスが大幅に減りました。こちらからレビューなどで指摘しても頑なに認めない例もあったりするので、hooks の block で現実を突きつけるのはプロンプティングとしても有用ではないかなと思います。
もし気に入った方がいれば、ぜひ使ってみて(そして改善も)ください。

また、フローの最後に "github に登録してある issue を解決して"、というようなpromptタスクを組み込んで、無限開発ループを作ることも可能ですが、長くなりすぎると Claude Code が stack level to deep な再帰リミットに達してしまうこともあるみたいなので、お勧めはしません。
本格的に無限タスク実行を設定する場合は Claude Code GitHub Actions を利用してフローを組むのが良いと思います。

...scriptで工夫した部分の技術的詳細は時間ができたら書きます

Discussion