Claude CodeでBiomeを使う — 編集直後にcheck --writeをhookで走らせる
Claude Codeがファイルを編集した直後にbiome check --writeを走らせるPostToolUse hookの設定と、直らなかった指摘をClaudeに返す書き方をまとめます。
Biomeをhookに載せると何が固定されるか
Biome(ビオーム)は、フォーマット・Lint・import整理などの修正案(assist actions)を1つのCLIで扱うツールです。biome checkはこの3つをまとめて検査し、--writeを付けると安全に直せる分をその場で書き換えます。これをClaude CodeのPostToolUse hookに載せると、Claudeがファイルを編集するたびに整形とLint修正が走ります。
ESLintとPrettierを別々に回す構成と比べて、hookに書くコマンドが1本で済みます。設定ファイルもbiome.json(またはbiome.jsonc)に寄せられます。手元のプロジェクトにBiomeを入れてある前提で、本記事ではhookの書き方、診断の出力量の絞り方、落とし穴を扱います。言語ごとの自動修正コマンドの比較はPostToolUse hookのLint自動修正の早見表にあります。
biome check --writeが適用する範囲
biome checkは、指定したファイルのフォーマット、Lint、assist actionsをまとめて検査します。PATHを省くとカレントディレクトリ以下が対象です。hookで使うときに関係するフラグを表にします。
| フラグ | 働き |
|---|---|
--write(別名--fix) | 働きフォーマット、安全なLint修正、安全なassist actionsを適用する |
--unsafe | 働き--writeと併用すると、安全でない修正も適用する |
--formatter-enabled / --linter-enabled / --assist-enabled | 働き3つの検査をコマンド単位で切り替える。値はtrueかfalse |
--enforce-assist | 働き必要なassist actionsが適用されないと失敗させる。既定はtrue |
--staged | 働きステージ済みのファイルだけを対象にする。ローカル用 |
--changed | 働きデフォルトブランチ(または--sinceの参照)との差分でコミット済みの変更だけを対象にする。CI用 |
hookで使うのは、実質--writeだけです。--stagedと--changedはGitの状態から対象を決める仕組みで、Claudeが今編集した1ファイルを狙う用途には合いません。--unsafeは後述する理由で、hookでは付けない構成にしています。
最小のhook設定
.claude/settings.jsonに、Edit|WriteにマッチするPostToolUse hookを書きます。コマンド本体はスクリプトに逃がし、パスは${CLAUDE_PROJECT_DIR}で指します。argsを付けるとシェルを介さず実行ファイルを直接起動する書き方(exec form)になり、パスに空白があっても引用符が要りません。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/biome-fix.sh",
"args": [],
"timeout": 60
}
]
}
]
}
}timeoutは秒単位で、command hookの既定は600秒です。Biomeは普通1ファイルなら短時間で終わるので、npxの初回取得で待たされるケースを見込んで60秒程度に短くしておくと、止まったときに気づきやすくなります。タイムアウトしたhookは出力が捨てられ、判定も返りません。
スクリプト本体 — 1ファイルだけ直して、残りをClaudeに返す
PostToolUseのhookにはstdinでJSONが渡されます。tool_input.file_pathに編集したファイルの絶対パスが入っているので、これをbiome check --writeに渡します。
mkdir -p .claude/hooks
cat > .claude/hooks/biome-fix.sh <<'SH'
#!/bin/bash
file_path=$(jq -r '.tool_input.file_path // empty')
[ -z "$file_path" ] && exit 0
output=$(npx @biomejs/biome check --write \
--no-errors-on-unmatched \
--files-ignore-unknown=true \
--diagnostic-level=error \
--reporter=concise \
"$file_path" 2>&1)
status=$?
if [ "$status" -ne 0 ]; then
echo "$output" >&2
exit 2
fi
exit 0
SH
chmod +x .claude/hooks/biome-fix.shnpxの代わりにネイティブバイナリを使う場合は、npx @biomejs/biomeをbiomeに置き換えます。Biomeの公式CLIページはどちらの呼び出しも並べて載せています。
スクリプトが1回動くときの流れ
- 1
file_pathを取り出す
jqでstdinのJSONから読みます。空なら対象外の入力なので、何もせずexit 0にします(matcherを広げたときの保険です)。 - 2
check --writeで直す
整形、安全なLint修正、import整理などが、その場でファイルに書き込まれます。
- 3
失敗したらstderrに書いて終了コード2
直せない指摘が残ったときだけ、出力をstderrへ流して
exit 2にします。PostToolUseでは終了コード2でもツールの実行は取り消せませんが、stderrの内容がClaudeに届きます。
終了コードを0のまま返すと、stderrはデバッグログにしか残らず、Claudeには見えません。指摘を読ませたいなら終了コード2が必要です。この仕組みの一般論はPostToolUse hookでツール実行後の後処理を自動化するにまとめています。
診断の量を絞る4つのフラグ
編集のたびにClaudeへ大量の診断を返すと、文脈を食うだけでなく、本題の指摘が埋もれます。スクリプトに入れたフラグの理由は次のとおりです。
スクリプトに入れたフラグの役割
--diagnostic-level=error
表示する診断の最小の重大度を決めます。既定は
infoで、errorにすると警告以下は表示されません。--reporter=concise
診断を1件1行にまとめる出力形式です。既定の
defaultは診断ごとに複数行の説明が付くため、stderrが長くなります。--files-ignore-unknown=true
Biomeが認識しないファイル種別で診断を出さなくなります。Markdownや画像を編集したときにhookが赤くなるのを防ぎます。
--no-errors-on-unmatched
処理対象のファイルが1つもないときにエラーを出しません。
.gitignoreなどで除外されたパスを渡したときに効きます。
診断の表示件数には--max-diagnosticsもあり、既定は20件です。数を絞りたいときは数値を渡し、制限を外したいときはnoneを渡します。1ファイルだけを対象にするhookでは20件で足りることが大半ですが、大量のLint違反が出る既存ファイルを初めて触るときは、先頭の20件しか返らない点を覚えておくと混乱しません。
警告も失敗扱いにしたい場合は--error-on-warningsを足します。このフラグは、警告の診断が1件でも出ると終了コードを失敗にします。警告を失敗扱いにするか、表示から外すかは別の方針なので、どちらにするかを先に決めます。
--unsafeを付けない理由
--writeが適用するのは「安全な」修正に限られます。--unsafeを足すと、安全と言い切れない修正やassist actionsまで書き換えます。コードの意味が変わりうる変更が、確認なしにClaudeの編集直後に入る構成になるため、hookでは付けません。必要なら人間が手元でbiome check --write --unsafeを実行し、差分を見てからコミットする運用が扱いやすくなります。
既存のESLint・Prettier設定から移す
ESLintとPrettierを使っているプロジェクトは、Biomeの設定を手書きせずに、設定の取り込みコマンドで下書きを作れます。biome migrateは引数なしだと、Biome自体の破壊的変更に伴う設定更新を表示するだけで、ファイルを書き換えません。--writeを付けて初めて適用されます。
npx @biomejs/biome init
npx @biomejs/biome migrate eslint --write
npx @biomejs/biome migrate prettier --writebiome initはカレントディレクトリに設定ファイルを作ります(--jsoncを付けるとbiome.jsonc)。migrate eslintはカレントディレクトリのESLint設定とignore設定を、migrate prettierはPrettierの設定とignore設定を取り込みます。ESLintの--include-inspiredを付けると、ESLintのルールに着想を得たBiomeのルールも移行対象に入ります。--include-nurseryではnurseryルールも含まれます。
移行したあとは、旧ツールのhookを残したままBiomeのhookを足さないようにします。同じファイルに2系統の整形と検査が走ると、指摘も二重にClaudeへ返ります。ESLint 9のflat configへ進める手順はClaude CodeでESLintのflat config移行を進めるで扱っています。
効かないケースと落とし穴
PostToolUse hookの仕様から、次のケースでは編集してもBiomeが走りません。
- Bashコマンドがファイルを書き換えたとき:
Edit|Writeにマッチするhookは、Bashコマンドや外部プロセスによる書き換えでは動きません。ディスク上の変更に反応させたいときはFileChangedイベントを使う選択肢があります - 失敗したツール呼び出し: PostToolUseはツールが成功したあとにだけ動きます。失敗時の処理は
PostToolUseFailureの領分です(PostToolUseFailure hookでツール失敗時だけ動くリカバリを書く) - Biomeが対応しない拡張子:
--files-ignore-unknown=trueを付けていれば診断は出ず、素通りします
もう1つ、設定ファイルの探索先は固定したほうが安全です。--config-pathを渡さなければ、Biomeはbiome.jsonかbiome.jsoncを探します。Claudeが作業ディレクトリを移すとhook入力のcwdも変わるため、参照する設定を1つに決めたいときは--config-pathにプロジェクトルートを渡します。環境変数BIOME_CONFIG_PATHでも同じ指定ができます。
CLAUDE.mdに添える規約
hookが整形とLint修正を引き受けるので、Claudeには「整形を手でやり直さない」と伝えておくと余計な編集が減ります。次は一例です。
## フォーマットとLint
- 保存時にhookが `biome check --write` を実行する。インデントや引用符の整形を手で直さない
- hookのstderrに指摘が出たら、その行だけを直す。`biome-ignore` コメントで黙らせる前に理由を説明する
- `--unsafe` は付けない二つ目の箇条にあるbiome-ignoreは、Biomeが診断を個別に抑止するコメントです。Claudeが警告を黙らせる近道として使うのを抑えるため、理由の説明を求める書き方にしています。
Claude Codeを起動せずにhookだけ試す
hookはstdinのJSONを読むだけなので、同じ形のJSONを流し込めば、Claude Codeなしで動作を確かめられます。パスは手元の実在ファイルに置き換えます。
printf '{"tool_name":"Write","tool_input":{"file_path":"%s/src/index.ts"}}' "$PWD" \
| .claude/hooks/biome-fix.sh; echo "exit=$?"整形だけで済むファイルならexit=0で、ファイルの内容が書き換わります。意図的に未使用変数などの違反を残したファイルなら、診断がstderrに出てexit=2になるはずです。Biome単体の挙動を先に確かめたいときは、公式CLIページが示す標準入力の形が使えます。
echo 'let a;' | npx @biomejs/biome check --stdin-file-path=file.js --write--stdin-file-pathは、標準入力のコードを処理して結果を標準出力に書く指定です。渡したパスは設定の選択と入力種別の判定に使われ、実在するファイルでなくても構いません。この形はファイルを書き換えないため、hookの本体には使わず、設定が効いているかの確認専用にします。
まとめ
Biomeのhookは、biome check --writeを1本呼ぶだけで整形、Lint修正、import整理を編集直後に固定できます。肝は、直らなかった指摘だけを終了コード2とstderrでClaudeに返す部分です。--diagnostic-level=errorと--reporter=conciseで出力を絞り、--unsafeは人間の手元に残す。この分担が、Claudeの文脈を汚さずに運用しやすい形です。