nikkie-ftnextの日記

イベントレポートや読書メモを発信

フックでリンタ(hatch fmt)のエラーを Claude Code に見せて、Python を理解している実装をさせる

はじめに

七尾百合子さん、お誕生日 224日目 おめでとうございます! nikkieです。

ついにリンタを渡します。
覚悟はいいか、Claude Code?

目次

厳しい Ruff としてのhatch fmt

これまでにフックで Ruff を実行できました。

私は Claude Code に Python を理解しているコードを書いてほしいと切に思っています。
例えば、ロギングに限っては f-string を使わない実装です。

これを達成するのにhatch fmtによるリントを採用します。
https://hatch.pypa.io/1.13/cli/reference/#hatch-fmt
hatch fmtは内部で Ruff を採用しており、非常に多くのルール1を有効にしてruff checkを流せます。
過去に役立った例がこちら:Ruff (flake8-simplify) に怒られて、イテラブルに条件を満たす要素を見つけ次第Trueを返すforループはanyと等価と初めて知りました - nikkie-ftnextの日記
私自身もhatch fmtのエラーメッセージに学ぶことも多く、Claude Code にもこのハードモードを歩ませようと決めました。

フックでリンタのエラーを Claude Code に見せる

ここが少しつまづいたポイントです。
結論を言うと、PostToolUseでリンタを流すときは

  • リンタの終了コードは2
  • 標準エラー出力にリンタのエラーメッセージを出力する

フックのリファレンスの「Hook出力

終了コード2ブロッキングエラー。stderrはClaudeに自動的に処理されるようにフィードバックされます。(シンプル:終了コード

PostToolUse stderrをClaudeに表示(終了コード2の動作

ワンライナーだと厳しくなってきたので、シェルスクリプトを書きました。

#!/usr/bin/env bash
set -u

hatch fmt "$1" 1>&2
exit_code=$?
if [ $exit_code -eq 1 ]; then
    exit 2
else
    exit $exit_code
fi

フックの設定(.claude/settings.local.json)はこちら。
環境変数CLAUDE_PROJECT_DIRを使います。
ref: プロジェクト固有のHookスクリプト

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.py$'; then \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/python_format.sh \"$file_path\"; fi; }"
          }
        ]
      }
    ]
  }
}

Claude Code が、ちゃんとした Python を書いてる!!

hatch fmtが通る Python を書いてくれて感無量です😭

あなたにはPEP8に準拠したPythonコードを書けるようにフックが設定されています。フックの出力を元に修正を繰り返してください。
それでは動作確認として、PEP8違反のコードを書いてみてください

できたコードはこちら

THRESHOLD = 10


def my_function(x, y, z):
    result = x + y + z
    if result > THRESHOLD:
        print("Result is greater than 10")  # noqa: T201
    else:
        print("Result is 10 or less")  # noqa: T201
    return result


class MyClass:
    def __init__(self, value):
        self.value = value

    def get_value(self):
        return self.value

hatch fmtに怒られながら、Claude Code が上記のコードに至った過程はこちら:
https://gist.github.com/ftnext/dc15d7310fd4baca94ffa70a33e0d247

「PLR2004: マジック値 10 を定数変数に置き換え」や「T201(print が見つかった)を noqa」しているのを見て、だいぶ制御できた感覚になりました。
美しい...

終わりに

Claude Code が書く Python をだいぶ美しくできました!
コードを書かせた後、「速いけどちっとも美しくない」ともやもやしなくて済みます。

積み残し事項も書いておきます

  • フックの配布の仕方(プラグイン?)
  • 全人類に Hatch を入れてもらうのか(より簡単にできないか)
  • 大元は Ruff なんですよね。ルールのカスタマイズどうしよう2

最後に、今回の環境情報です

% claude --version
2.0.27 (Claude Code)
% hatch --version
Hatch, version 1.14.1

  1. 631 selected stable rules and 112 selected preview rules.」ref: https://hatch.pypa.io/1.13/config/internal/static-analysis/#selected-rules
  2. Ruff のプラグイン、マジできてくれ〜