Claude CodeでRedocly CLIのbundleとlintを回す — 分割したOpenAPIの運用設定
$refで分割したOpenAPIをRedocly CLIでlintしてbundleし、単一ファイルを出すまでを、PostToolUse hookとredocly.yamlの設定例でまとめます。
大きなOpenAPI定義は、1ファイルのまま育てるとClaude Codeにも人にも扱いづらくなります。$refでpathsやcomponentsを別ファイルに分け、配布用に1ファイルへまとめる運用が定番です。その分割と結合を担うのがRedocly CLIのsplitとbundleで、lintも同じ道具で回せます。
この記事では、分割したOpenAPIをredocly.yamlで管理し、Claude Codeが編集するたびにlintとbundleが走る構成を作ります。bundleで出した単一ファイルをClaude Codeに直接編集させない運用まで含めます。Spectralでlintだけを回したい場合はSpectralのPostToolUse hookで足ります。
split・bundle・joinは何が違うのか
Redocly CLIには、OpenAPIファイルを組み替えるコマンドが3つあります。名前が似ているので、入力と出力で区別します。
| コマンド | 入力 | 出力 |
|---|---|---|
split | 入力1つのOpenAPIファイル | 出力paths・componentsなどに分かれた複数ファイル |
bundle | 入力ルートのOpenAPIファイル | 出力$refをたどって集めた単一ファイル(JSONまたはYAML) |
join | 入力複数のOpenAPIファイル | 出力1つの統合されたAPI定義 |
bundleはルートファイルから$refをたどり、参照されている部品を自動で取り込みます。部品を明示的に列挙する必要はありません。joinは別々のAPI定義を1つのAPIにまとめるコマンドです。1つのAPIを分割管理しているなら使うのはbundleです。
splitで分けた構成はbundleでそのまま戻せます。開発中は小さなファイルで編集とレビューをし、他のツールに渡すときだけ単一ファイルにする。この使い分けが、Claude Codeと組み合わせる前提になります。
分割した構成とredocly.yamlを用意する
既存の1ファイル定義から始めるなら、splitでディレクトリ構成を作れます。OpenAPI 3.xでは、pathsとwebhooksは1件ずつ、componentsは種類ごとのサブディレクトリに分かれます。
npm i -D @redocly/cli@latest
npx redocly split openapi.yaml --outDir=openapi生成される構成は次のようになります。
openapi/
openapi.yaml # ルート。$refで下を参照する
paths/ # パスごとに1ファイル
components/
schemas/
parameters/
responses/パスのファイル名は、URLの/を区切り文字(既定は_)に置き換えて決まります。--separatorで区切り文字を変えられます。大文字小文字だけが違う名前(Userとuser)は、大文字小文字を区別しないファイルシステムで衝突するため、後から出たほうにuser-2.yamlのような番号が付きます。--file-name-conflicts-severity=errorを付けると、警告で済ませずsplit自体を失敗させられます。
redocly.yamlでルートと出力先を固定する
コマンドにパスを毎回渡すと、Claude Codeが別のパスを渡す余地が生まれます。プロジェクト直下のredocly.yamlに、ルートファイルと出力先を書いておくと引数が要らなくなります。
extends:
- recommended
apis:
main:
root: openapi/openapi.yaml
output: dist/openapi.yaml
rules:
info-license: off
tag-description: errorapisの下にAPI名を付け、rootにルート、outputにbundleの出力先を書きます。この状態でredocly lintとredocly bundleを引数なしで実行すると、設定済みの全APIが対象になります。逆に、コマンドへAPIを渡した場合は、そのAPIだけが処理されます。--outputが意味を持つのも、コマンドラインで渡したAPIに対してだけです。
bundleで単一ファイルを出す
最小の実行は次の1行です。ルートファイルを渡し、出力先を-oで指定します。
npx redocly bundle openapi/openapi.yaml -o dist/openapi.yaml出力は既定でYAMLです。JSONにしたいときは--ext jsonを付けるか、出力名を.jsonにします。--outputは既存の出力ファイルを上書きします。
使う場面が分かれるオプション
| 場面 | オプション |
|---|---|
$refを残さず全部展開したい | オプション--dereferenced(-d) |
| 使われていないcomponentsを落としたい | オプション--remove-unused-components |
| エラーがあっても出力だけは欲しい | オプション--force(-f) |
| 同名で中身の違うスキーマを失敗にしたい | オプション--component-renaming-conflicts-severity=error |
--dereferencedは、$refを理解しないツールに渡す場合に使います。JSON出力は循環参照があると使えない、とコマンドのページに注記があります。
同名スキーマの衝突は見落としやすい点です。外部ファイルから取り込んだスキーマに同じ名前で中身の違うものがあると、bundleは既定で警告を出し、重複側をSchema-2のように自動で改名します。警告のまま通ってしまうので、Claude Codeがスキーマを足したときに別名へすり替わっても気づけません。--component-renaming-conflicts-severity=errorにすれば、衝突した時点でbundleが失敗します。
名前の付け方そのものを変える手もあります。--component-names-strategy=titleは、各スキーマのtitleをPascalCaseに変換して名前にします。ファイルのパスや$refの順序に左右されないので、番号付きの名前が後から変わる事態を避けられます。ただしtitleの無いスキーマはエラーになります。
lintのルールをredocly.yamlに書く
redocly lintは、OpenAPIの問題を検査するコマンドです。ルールセットを指定しなければrecommendedが使われ、redocly.yamlにextendsやrulesがあればそちらが優先されます。組み込みのルールセットにはspec・minimal・recommended・recommended-strictがあります。
rulesにはルール名: 重大度の短縮形か、追加オプション付きの形式で書きます。
rules:
tag-description: error
boolean-parameter-prefixes:
severity: error
prefixes:
- is
- can重大度はerror・warn・offのいずれかです。自作のルールはrule/で始まる名前でsubjectとassertionsを書きます。たとえばoperationIdをキャメルケースに揃えるルールは、次のように書けます。
rules:
rule/operationId-casing:
subject:
type: Operation
property: operationId
assertions:
casing: camelCaseClaude Codeにとっては、規約を文章で渡すよりルールで渡すほうが確実です。守れていなければ、ファイルと行を添えた指摘が返るからです。
指摘の見せ方を変える
出力形式は--formatで選べます。既定はcodeframeで、stylishにすると1件が1行に収まり、hookでClaude Codeに返す量を抑えられます。ほかにjson・github-actions・markdown・junitなどがあります。表示件数は既定で100件までで、--max-problemsで変えられます。
既存の定義に指摘が大量にある状態から始めるなら、--generate-ignore-fileが使えます。実行時点のerrorとwarningを.redocly.lint-ignore.yamlに書き出し、以後は新しく増えた指摘だけを見られます。ただし生成された無視リストは負債の先送りなので、Claude Codeには「無視リストへの追記は禁止」と伝えておく必要があります。
プレビューはpreviewではなくbuild-docsで
「プレビュー」には取り違えやすい点があります。redocly previewは、Redocly製品のプロジェクト(@redocly/framework)用のローカルサーバーを起動するコマンドです。ポートは既定で4000番です。単一のOpenAPIファイルを見たいだけなら、build-docsが向いています。
npx redocly build-docs dist/openapi.yaml -o preview/index.htmlbuild-docsはRedocでAPIドキュメントを描画した、独立したHTMLファイルを作ります。出力先の既定はredoc-static.htmlです。生成したHTMLは、Redocのスクリプトを既定でRedocly CDNから読み込みます。--inlineBundleを付ければHTMLに埋め込めて、CDNに接続できない環境でも表示できます。ページからはRedocのテレメトリが読者のブラウザーから送られるため、不要ならREDOCLY_TELEMETRY=offで生成します。
HTMLはhookのたびに作る必要はありません。見た目の確認が要るときに、Claude Codeへ「bundleしてからbuild-docsで確認用HTMLを作って」と頼めば足ります。
編集のたびにlintとbundleを走らせるhook
ここからが本題です。PostToolUseはツール実行後に動くhookで、Edit|Writeのmatcherを付ければファイル編集の直後だけに絞れます。コマンドには、編集したファイルのパスがJSONで標準入力から渡されます。
hookスクリプト
.claude/hooks/redocly-check.shとして保存し、実行権限を付けます。
#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
*/dist/*) exit 0 ;;
*/openapi/*.yaml|*/openapi/*.yml) ;;
*) exit 0 ;;
esac
cd "$CLAUDE_PROJECT_DIR" || exit 0
OUT=$(npx redocly lint 2>&1 \
&& npx redocly bundle 2>&1)
if [ $? -ne 0 ]; then
echo "$OUT" >&2
exit 2
fi分割したファイルのどれを編集しても、lintとbundleの対象はredocly.yamlのルートです。断片だけを単体でlintすると、$refの解決先が足りず意味のある結果になりません。ルートから見て検査するほうが、断片の変更が全体に与える影響まで拾えます。dist/配下はbundleの出力なので、先頭で除外しています。
終了コードを2にしているのは、PostToolUseの時点でツールはすでに実行済みで、止められないからです。それでも終了コード2で終えれば、標準エラー出力がClaude Codeに表示されます。終了コード0で終えたhookの標準エラー出力はClaude Codeには見えないため、失敗を伝えたいときは2で抜けます。
lintのページには終了コードの記載がありません。導入時に、errorを含むファイルでnpx redocly lint; echo $?を実行し、0以外になることを確かめてください。warningだけの場合に0で終わるかどうかも、同じ方法で見られます。
settings.jsonへの登録
プロジェクトの.claude/settings.jsonに、次のように登録します。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/redocly-check.sh"
}
]
}
]
}
}登録後は/hooksで表示を確認できます。動作確認は、パスのファイルから必須の項目を消す編集をClaude Codeにさせ、次の応答でlintの指摘を踏まえた修正が走るかを見るのが早いです。
CLAUDE.mdで「編集してよい場所」を決める
hookは事後に指摘を返す仕組みです。そもそも間違った場所を編集させないために、CLAUDE.mdへ運用の約束を書いておきます。
## OpenAPI
- 編集してよいのは `openapi/` 配下の分割ファイルだけ。`dist/` は直接編集しない
- 変更後は `npx redocly lint` と `npx redocly bundle` が通ること
- `.redocly.lint-ignore.yaml` に指摘を追記して通さない。直せない場合は理由を報告する
- スキーマを追加するときは、既存の名前と衝突しない名前にするbundleの出力を編集してしまうと、次のbundleで上書きされて変更が消えます。生成物を直させない考え方は、OpenAPI Generatorの生成コードを直させず再生成させる設定と同じです。定義そのものの安全性まで監査したいときは、42Crunchプラグインによる監査と修正が補完になります。
つまずきやすい点
ここまでの構成で起こりやすい失敗は次のとおりです。
- Nodeが古くて動かない: v2はNode.js v22.12.0以上が必要。hookの実行環境で
node -vを確かめる - bundleだけ警告で通る: 同名スキーマの衝突は既定で警告止まり。
--component-renaming-conflicts-severity=errorで失敗にする - 断片を単体でlintしている: ルートファイルを対象にする。引数なしの
redocly lintは設定済みの全APIが対象 --forceで通している: エラーがあっても出力されるため、hookのコマンドには付けない- distが古い: 編集ツール以外で断片を変えた場合はhookが動かない。手でbundleし直す
lintとbundleを別々の命令にせず、1つのhookにまとめるのが、この構成の要点です。指摘が出たら直し、通ったらdistが最新になる。その往復をClaude Codeが自分で回せるようになります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。