Claude Codeのcommitlint設定 — 規約違反のコミットを差し戻す
commitlintのcommit-msg hookでClaude Codeが書いたコミットメッセージの規約違反を差し戻し、自分で直させる設定と、設定ファイルが読み込めない落とし穴、CLAUDE.mdの書き方をまとめます。
Claude Codeに任せたコミットは、メッセージの形が日によってぶれます。Added login page.と書く日もあれば、feat: add login pageと書く日もあります。CLAUDE.mdに規約を書けば減りますが、守られる保証はありません。
そこで、メッセージの検査をGitのcommit-msgフックに置きます。commitlintが規約違反のコミットを差し戻し、Claudeが理由を読んで書き直します。この記事では、その最小構成と、実際に踏んだ落とし穴を順に説明します。
commit-msgフックで規約違反を差し戻す
commitlintは、コミットメッセージがConventional Commitsなどの規約に合っているかを調べるCLIです。公式のローカル設定ガイドは、commit-msgフックで動かす構成を勧めています。pre-commitフックには対応しておらず、フックのファイル名はcommit-msgでなければなりません。
Claude Codeから見ると、これは普通のGitフックです。git commitが失敗し、commitlintの出力がコマンドの結果として返ります。Claudeはその出力を読み、メッセージを直して再実行できます。
導入から差し戻しまでの流れ
- 1
パッケージを入れる
@commitlint/cliと共有設定の@commitlint/config-conventionalを開発依存として入れます。フックの管理にはhuskyを使います。 - 2
設定ファイルを置く
extendsで共有設定を指定します。形式の選び方は次の節で説明します。 - 3
commit-msgフックを置く
.husky/commit-msgにcommitlint --edit $1を書きます。$1はGitが渡すメッセージファイルのパスです。 - 4
Claudeに直させる
違反するとコミットは作られず、ステージした変更はそのまま残ります。Claudeはメッセージだけを直して再コミットします。
設定ファイルとフックを用意する
公式の手順に沿ってパッケージを入れます。
npm install -D @commitlint/cli @commitlint/config-conventional husky
npx husky initnpx husky initはpre-commitフックの雛形も作ります。メッセージ検査だけが目的なら、その雛形が不要なテスト実行を呼んでいないか確認してください。huskyとpre-commitを併用するときの入口の揃え方は、huskyとpre-commitを併用する記事で扱っています。
次にcommit-msgフックを書きます。公式の例そのままです。
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msgcommitlint.config.jsに書いたexport defaultが動かない
公式のGetting startedは、設定ファイルを次のコマンドで作るよう案内しています。
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.jspackage.jsonに"type": "module"がないプロジェクトで、@commitlint/cli 20.5.3をNode 20.18.0で動かすと、次のエラーで止まりました。
commitlint.config.js:1
export default { extends: ['@commitlint/config-conventional'] };
^^^^^^
SyntaxError: Unexpected token 'export'この環境では、typeのない.jsファイルがCommonJSとして読まれるためです。新しいNodeには、typeのない.jsでもESM構文を検出して読み込むものがあるので、Nodeのバージョンによってはこのエラーが出ない場合があります。エラーが出たときの対処は2つあります。
- ファイル名を
commitlint.config.mjsにする package.jsonに"type": "module"を足す
どちらもexport defaultのまま使えます。Claudeに設定ファイルを作らせるときは、プロジェクトのtypeを先に見るよう伝えると、この失敗を避けられます。
違反したときにClaudeが見る出力
設定を置いたら、わざと規約に反するメッセージでコミットします。@commitlint/cli 20.5.3での出力です。
git commit -m "Added login page."⧗ input: Added login page.
✖ subject may not be empty [subject-empty]
✖ subject may not end with full stop [subject-full-stop]
✖ type may not be empty [type-empty]
✖ found 3 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky - commit-msg script failed (code 1)見るべきは各行末の[ルール名]です。Claudeは違反したルールを特定でき、人間もルール名で設定を検索できます。type-emptyとsubject-emptyが同時に出るのは、Added login page.がtype: subjectの形をしていないためです。
この出力は、次のようなメッセージの直し方をClaudeに教えます。
| 出力の例 | 意味 | Claudeの直し方 |
|---|---|---|
type may not be empty | 意味feat:などの接頭辞がない | Claudeの直し方先頭に型を付ける |
type must be one of [...] | 意味型が許可リストにない | Claudeの直し方一覧の中から選び直す |
subject must not be sentence-case... | 意味件名の先頭が大文字 | Claudeの直し方小文字で始める |
header must not be longer than 100 characters | 意味1行目が長い | Claudeの直し方件名を縮め、詳細は本文へ移す |
検査に通ったコミットは、何も出力しません。結果を見たいときは--verboseを付けます。
共有設定が何を縛るのか
@commitlint/config-conventionalは、Conventional Commitsに加えて、Angularの規約に由来する書式のルールも含みます。READMEによると、仕様が求めない部分は次のとおりです。
type-enum: 型をbuildchorecidocsfeatfixperfrefactorrevertstyletestの11個に限るheader-max-lengthbody-max-line-lengthfooter-max-line-length: 1行を100文字までにする。URLを含む本文とフッターの行は対象外type-casesubject-casesubject-full-stop: 型は小文字、件名はsentence-caseなどにせず、末尾にピリオドを付けない
ルールには水準があり、0は無効、1は警告、2はエラーです。body-leading-blankとfooter-leading-blankは警告扱いです。手元で本文の前の空行を抜いて試すと、body must have leading blank lineと警告は出ましたが、終了コードは0で、コミットは通りました。
自分のリポジトリの規約に合わせて上書きする
既にadd: update: fix:のような独自の接頭辞を使っているなら、rulesで上書きします。
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['add', 'update', 'fix', 'remove', 'refactor', 'docs', 'chore', 'test']],
'header-max-length': [2, 'always', 72],
},
};この設定で試すと、add: ログイン画面を追加は通り、feat: add loginはtype must be one of [add, update, ...]で差し戻されました。update: Update READMEは件名が大文字始まりなのでsubject-caseに引っかかります。
日本語の件名では、2つの挙動に注意してください。
subject-caseは英字の大文字小文字の規則なので、日本語の件名は影響を受けませんsubject-full-stopの対象は半角のピリオドだけで、fix: ユーザー登録時のエラーを修正する。は通りました。subject-full-stopの値を'。'に変えると、句点を禁止できる代わりに、半角ピリオドは検査されなくなります
モノレポではscopeも縛れる
scope-enumとscope-emptyを足すと、fix(web): ...のように変更範囲の記載を必須にできます。許可リストをweb api docsにして試した結果は次のとおりです。
| メッセージ | 結果 |
|---|---|
fix(web): ボタンの色を直す | 結果通る |
fix(cli): ボタンの色を直す | 結果scope must be one of [web, api, docs]で差し戻し |
fix: ボタンの色を直す | 結果scope may not be emptyで差し戻し |
scopeの一覧はパッケージ名やディレクトリ名と対応します。Claudeが新しいパッケージを足したときは、commitlint.config.mjsの一覧も同じコミットで更新させないと、次のコミットが自分で作った設定に落とされます。
本文の行長は文字数で数える
body-max-line-lengthの100は、表示幅ではなく文字数です。日本語の本文を試すと、全角の「あ」90文字の行は通り、120文字の行はbody's lines must not be longer than 100 charactersで差し戻されました。全角100文字は、英字100文字の2倍の幅になります。画面で収まりが良いかは別の問題なので、日本語のリポジトリでは行長を緩めるか、80前後に揃えるかをルールで決めておくと迷いません。
CLAUDE.mdには検査の存在と直し方を書く
フックだけでも差し戻しはできます。ただ、毎回1回目で落ちると、Claudeがやり直す手間とトークンが増えます。規約の中身を先に伝えておけば、1回目から通る率が上がります。
## コミットメッセージ
- 形式は `<type>: <件名>`。type は add / update / fix / remove / refactor / docs / chore / test のいずれか
- 件名は日本語で、1行目は72文字以内
- commit-msg フックの commitlint で検査される。落ちたら出力の `[ルール名]` を読み、メッセージだけを直して再コミットする
- 変更内容やステージ状態は変えない。メッセージのためにコミットを分け直さない
- `--no-verify` でフックを飛ばさない最後の1行は願いごとにすぎません。フックを飛ばす経路を実際に止めたいときは、PreToolUse hookでBashコマンドを検査します。設定例はhuskyとpre-commitの併用記事にあります。
コミット前にClaude自身で検査させる
commitlintは標準入力からメッセージを受け取れます。Claudeにコミットする前に自分でメッセージを検査させれば、差し戻しの往復をなくせます。公式のローカル設定ガイドにも、標準入力に流して試す方法が載っています。設定ファイルがある場合は、次のとおりです。
echo "fix(web): ボタンの色を直す" | npx commitlint何も出力されず終了コードが0なら、規約に合っています。CLAUDE.mdには「git commitの前に、メッセージをnpx commitlintへ流して確認する」と1行足せば足ります。ただし、これはあくまで先回りの確認です。最終的な強制はcommit-msgフックが担うので、確認を飛ばされても規約違反は通りません。
Claude Codeが付ける署名行は通る
Claude Codeは既定でコミットにCo-Authored-Byなどのトレーラーを付けます。この形式はattribution.commitで変更でき、falseで全て隠せます。
署名行が規約検査で落ちないか心配になりますが、手元では次のメッセージが警告もエラーもなく通りました。
feat: x
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ABC署名の長さが問題になるのは、attribution.commitに100文字を超える文字列を設定したときです。URLを含む行はfooter-max-line-lengthの対象外ですが、URLのない長い行は引っかかります。署名の設定そのものは、attributionでコミット署名を変える記事で扱っています。
つまずきやすい点
- フックがインストールされていない:
npx husky initはpackage.jsonに"prepare": "husky"を足し、core.hooksPathが.husky/_を指す形になりました。クローン直後でnpm installを実行していないリポジトリでは、この設定が入らず、コミットが検査なしで通ることがあります git commit --amend -mも検査される: メッセージを書き換えるコミットでもcommit-msgフックが動きます。bad messageを試すとsubject-emptyとtype-emptyで止まりました- 既存のコミットをさかのぼって調べる:
npx commitlint --last --verboseで直近のコミットを、--fromと--toで範囲を検査できます - ローカルの検査は回避できる: 公式ガイドも、ローカルの検査は手軽に回避できると注意しています。確実にしたいなら、同じ
commitlintをCIで--from--to付きで動かします
Lintやテストの失敗を先回りで直したいなら、Claude側のhookが役に立ちます。PostToolUse hookのLint自動修正と組み合わせると、メッセージとコードの両方が揃います。コミットからPR作成までをまとめて任せる場合は、commit-commandsプラグインが生成するメッセージにも、同じ検査が掛かります。
まとめ
commitlintはcommit-msgフックで動かし、Claudeには差し戻しの出力を読ませるだけで、メッセージの書式を安定させられます。設定の落とし穴は、公式の手順どおりに作ったcommitlint.config.jsが、type: moduleのないプロジェクトでは、Nodeのバージョンによって読み込めない点です。規約はrulesで自分のリポジトリに合わせて上書きでき、Claude Codeの署名行は既定の形式なら通ります。
CLAUDE.mdには型の一覧と直し方の方針を書き、強制したい部分だけをフックに任せる分担が、手戻りの少ない構成です。