🤹

Claude Codeが書いたコードに自動でlintやrubocopをかけるようにhooksを設定した

に公開

TimelabLynx というカレンダーサービスを開発している takahashi(@stak_22)です。

Lynx 開発チームでは Claude Code に一点集中してナレッジを深めています。
今回は、Claude Codeが書いたコードに対して自動でlintやrubocopをかけるようにhooksを設定しました。

何ができるようになるの?

一言で言うと、Claude Codeが作業を完了すると自動でlintやrubocopをチェックしてくれるようになります。(今回はそれを通知させる仕組みも構築しました)

Claude Codeにプロンプトを与えて、コードを修正させます。その後lintやrubocopでエラーが出ていても気にせずcommit, pushされてしまうことがあると思うのですが、GitHubのciを待ってから確認するのもタイムロスです。そのため、Claude Codeがコードを変更した際、勝手にlintやrubocopをチェックしてくれれば効率が上がるのでは?と思ったのが経緯です。

今回はチェックしたら通知させるようにしました。

hooksとは?

シェルコマンドを登録してClaude Codeの動作をカスタマイズおよび拡張する方法

とのこと。
つまり、Claude Codeの動作タイミングに応じて、開発者が行わせたい処理を引っ掛けることができる仕組みです。
今回でいうと、「Claude Codeが処理を完了したタイミングで、lintやrubocopを走らせる」という感じです。

詳しくは、Anthropic の Claude Code hooks に関するページをご覧ください。使用例にもある通り、本記事の目的はわりと基本的な使用方法です。

フックの使用例には以下が含まれます:

  • 通知: Claude Codeがあなたの入力や何かを実行する許可を待っているときに通知を受ける方法をカスタマイズします。
  • 自動フォーマット: すべてのファイル編集後に.tsファイルでprettierを実行し、.goファイルでgofmtを実行するなど。

どうやって設定するの?

フックイベント概要 をご覧ください。
フックを実行させるタイミングを選べます。
今回は、

Stop: Claude Codeが応答を終了するときに実行されます

このタイミングで引っ掛けます。

この公式docにある「クイックスタート」の通り設定することで実現可能ですが、プロジェクトの設定ファイルに記述してしまうのが手っ取り早い(結局はファイルが作成される)ので、そちらを紹介します。
プロジェクトの.claude/settings.jsonに以下を記述します。(これは.claude/settings.local.jsonでも~/.claude/settings.jsonでも大丈夫です)

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use-lint.sh"
          }
        ]
      }
    ]
  }
}

これは、Stopのタイミングでcommand(シェルスクリプト)が実行される設定です。
そして .claude/hooks/post-tool-use-lint.sh を作成します。
$CLAUDE_PROJECT_DIR は、プロジェクトに保存されたスクリプトを参照し、Claudeの現在のディレクトリに関係なく動作してくれるぽいです)

※monorepoなのでフロントとバックエンド二つを同時に設定してます

#!/bin/bash

# hook script - git diffベースで実行
# 差分がある場合のみ、該当するプロジェクトのlintまたはRubocopを実行します

# 通知を表示する関数
show_notification() {
    local message="$1"
    local title="${2:-Lint Hook}"
    osascript -e "display notification \"$message\" with title \"$title\""
}

# /front の差分チェックとlint実行
if ! git diff --quiet front/; then
    cd front
    
    # まずlintを実行してエラーをチェック
    LINT_OUTPUT=$(pnpm lint 2>&1)
    LINT_EXIT_CODE=$?
    
    if [ $LINT_EXIT_CODE -ne 0 ]; then
        # エラーがある場合、lint:fixを実行
        FIX_OUTPUT=$(pnpm lint:fix 2>&1)
        FIX_EXIT_CODE=$?
        
        if [ $FIX_EXIT_CODE -ne 0 ]; then
            # lint:fixでも修正できない場合
            show_notification "Some lint issues couldn't be fixed automatically!" "❌ Vue.js Lint Error"
        else
            # lint:fixで修正された場合
            show_notification "Lint issues were fixed automatically. Check the changes!" "✅ Vue.js Lint Fixed"
        fi
    fi
    cd ..
fi

# バックエンド(Rails)の差分チェックとRubocop実行
CHANGED_RUBY_FILES=$(git diff --name-only --diff-filter=ACMR | grep -E '\.(rb|rake)$' | grep -E '^(app|config|lib|spec)/' || true)
if [ -n "$CHANGED_RUBY_FILES" ]; then
    # Rubocopを実行して結果を取得
    RUBOCOP_OUTPUT=$(docker compose run --rm web rubocop -- "${CHANGED_RUBY_FILES[@]}" 2>&1)
    RUBOCOP_EXIT_CODE=$?
    
    # Rubocopの結果に基づいて通知
    if [ $RUBOCOP_EXIT_CODE -ne 0 ]; then
        # 警告またはエラーが見つかった場合
        OFFENSE_COUNT=$(echo "$RUBOCOP_OUTPUT" | grep -E "[0-9]+ offenses? detected" | grep -oE "[0-9]+" | head -1)
        if [ -n "$OFFENSE_COUNT" ]; then
            show_notification "$OFFENSE_COUNT offenses detected! Check the output." "⚠️ Rubocop Warning"
        else
            show_notification "Rubocop failed! Check the output." "❌ Rubocop Error"
        fi
    fi
fi

^ 動作としては、以下の挙動になります。

  • lint(/front配下で差分があれば動作)
    • lintが成功 → 何もしない
    • lintが失敗 → lint:fixコマンドを実行 → 成功失敗それぞれの結果を通知
  • rubocop(rbなどで差分があれば動作)
    • rubocopが成功 → 何もしない
    • rubocopが失敗 → 失敗の結果を通知

通知は、失敗したときか修正がかかったときのみ飛んでくるようにしているので、ちょうどよく便利です。

感想

こんな感じで、hooksはスクリプトを書けば色々できました。
今後も引き続きいろいろ試して改善していきたいと思います。

Appendix

macOSで通知するコマンド display notification が動作せずだったのですが、スクリプトエディタで一度実行すると通知許諾がされるので、それをオンにすれば行けました。
@ref https://www.macscripter.net/t/trying-to-use-terminal-for-display-notification/76593?ref=blog.lai.so

Timelabテックブログ

Discussion