Claude Media
Claude CodeでOpenAPIをSpectralでlintする — 編集のたびに検証させる設定

Claude CodeでOpenAPIをSpectralでlintする — 編集のたびに検証させる設定

OpenAPI定義の編集後にSpectralのlintを自動で走らせ、指摘をClaude Codeへ返す設定を、PostToolUse hookとCLAUDE.mdの書き方まで手順でまとめます。

OpenAPI定義をClaude Codeに編集させるなら、編集のたびにSpectralで検証させる構成が確実です。Spectralは、OpenAPIなどのJSON/YAMLをルールで検査するlinterです。ルールセットにチームの設計規約を書いておけば、Claude Codeの修正が規約に合っているかを機械が毎回判定します。

この記事では、PostToolUse hookでlintを自動実行し、指摘をClaude Codeへ返して直させるところまでを手順にします。CLAUDE.mdへ何を書くか、警告だけでは止まらない終了コードの罠も扱います。

Spectralでlintする3つの部品

Spectralは「ruleset(ルール集)」「CLI」「出力形式」の3つで動きます。まず役割を押さえます。

部品役割本記事での置き場所
ルールセット役割どの項目をどう検査するかの定義本記事での置き場所リポジトリ直下の.spectral.yaml
CLI役割spectral lintで定義を検査する本記事での置き場所@stoplight/spectral-cli
hook役割編集の直後にCLIを呼ぶ本記事での置き場所.claude/hooks/spectral-lint.sh

Spectralは汎用のYAML/JSON linterで、ルールセットが無いと何も検査しません。プロジェクトのREADMEは、まず.spectral.yamlを作る手順を最初に置いています。ルールセットはJSON、YAML、JavaScript/TypeScriptのいずれかで書けます。

インストールとルールセットの作成

CLIはnpmでインストールします。プロジェクトに閉じる場合は-Dで開発依存にします。

npm install -D @stoplight/spectral-cli
npx spectral --version

Spectralにはspectral:oas(OpenAPI v2/v3)、spectral:asyncapi、spectral:arazzoの3つの組み込みルールセットがあります。OpenAPIだけを検査するなら、次の1行で足ります。

echo 'extends: ["spectral:oas"]' > .spectral.yaml
npx spectral lint openapi/openapi.yaml

--rulesetを省略すると、CLIはカレントディレクトリの.spectral.yml、.spectral.yaml、.spectral.json、.spectral.jsのいずれかを探します。見つからなければ、そのドキュメントは検査されません。hookをプロジェクト外のディレクトリから呼ぶと、エラーにもならずlintが素通りする理由がここにあります。後述のhookでは--rulesetを明示します。

空のOpenAPIに何が指摘されるか

手元のSpectral 6.16.3で、infoにタイトルとバージョンだけを書いた最小のOpenAPI 3.0.3を検査すると、組み込みルールセットは次の警告を返しました。

 1:1  warning  oas3-api-servers       OpenAPI "servers" must be present and non-empty array.
 2:6  warning  info-contact           Info object must have "contact" object.
 2:6  warning  info-description       Info "description" must be present and non-empty string.
 7:9  warning  operation-description  Operation "description" must be present and non-empty string.
 7:9  warning  operation-tags         Operation must have non-empty "tags" array.

どれもwarnです。info-contactやoperation-descriptionのように、組み込みルールの多くは既定で有効かつwarnの重大度で報告されます。この事実が、次の終了コードの話につながります。

警告だけでは終了コードが0になる

hookでlintを自動化するとき、最初に踏む罠です。SpectralのCLIにはerror、warn、info、hintの4段階の重大度があります。既定ではerrorが1件でもあれば終了コード1で終わり、警告だけなら0で終わります。

実行警告のみのときの終了コード
spectral lint openapi.yaml警告のみのときの終了コード0
spectral lint openapi.yaml --fail-severity warn警告のみのときの終了コード1

上の表は、上で使った最小定義にパスの形とsummaryを直してerrorをゼロにした状態で、手元で実測した値です。終了コードを見るhookは、既定のままだと警告を全部見逃します。--fail-severity warnを付ければ、警告以上で失敗扱いになります。--display-only-failures(-D)を足すと、出力もその重大度以上に絞れます。

どこまでを失敗にするかは、ルールセット側でも調整できます。extendsしたルールの重大度だけを変える書き方が用意されています。

extends: ["spectral:oas"]
rules:
  operation-success-response: error
  info-contact: off

rulesにルール名: 重大度だけを書くと、元のルールの定義を残したまま重大度を差し替えられます。offで無効化もできます。既存の定義に警告が数十件ある状態から始めるなら、まず問題の大きいルールだけをerrorに上げ、残りはwarnのまま段階的に直していくやり方が現実的です。

自分たちの設計規約をルールにする

組み込みルールが見るのは、主にOpenAPIとして破綻していないかどうかです。命名や必須項目といったチーム固有の規約は、自作ルールで書きます。ルールの基本形は、公式のREADMEに載っている「パスをkebab-caseにする」例のとおりです。

extends: ["spectral:oas"]
rules:
  paths-kebab-case:
    description: パスは kebab-case にする
    message: "{{property}} は kebab-case にしてください"
    severity: error
    given: $.paths[*]~
    then:
      function: pattern
      functionOptions:
        match: "^(\\/|[a-z0-9-.]+|{[a-zA-Z0-9_]+})+$"
  operation-summary-required:
    description: 全オペレーションに summary を付ける
    severity: error
    given: $.paths[*][get,post,put,patch,delete]
    then:
      field: summary
      function: truthy

givenはJSONPathで検査の対象を選び、thenで関数を適用します。上のファイルで、パスが/userProfilesでオペレーションにsummaryが無い定義を検査すると、手元では次の2件がerrorで出ました。

 6:17    error  paths-kebab-case            /userProfiles は kebab-case にしてください
 7:9     error  operation-summary-required  全オペレーションに summary を付ける

ここまでを組み合わせると、spectral:oasの警告が5件、自作ルールのエラーが2件の合計7件になります。operation-summary-requiredは、公式の例にあるpatternと同じ型で、組み込み関数のtruthyをsummaryフィールドに当てたものです。

Claude Codeの側から見ると、ルールは規約の文章よりも強い指示になります。CLAUDE.mdに「パスはkebab-case」と書くだけでは守られたかどうかが分かりません。ルールにすれば、守られていないときに具体的な行番号付きで返せます。

PostToolUse hookで編集のたびにlintを走らせる

Claude Codeのhookは、ツール呼び出しの前後にコマンドを走らせる仕組みです。PostToolUseはツールが成功した後に動き、matcherにEdit|Writeを指定すればファイル編集の直後だけに絞れます。コマンドには、編集対象のパスがJSONで標準入力から渡されます。

hookスクリプト

.claude/hooks/spectral-lint.shとして保存し、実行権限を付けます。

#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
  */openapi/*.yaml|*/openapi/*.yml|*/openapi.yaml) ;;
  *) exit 0 ;;
esac
OUT=$(npx spectral lint "$FILE" \
  --ruleset "$CLAUDE_PROJECT_DIR/.spectral.yaml" \
  --fail-severity warn --display-only-failures 2>&1)
if [ $? -ne 0 ]; then
  echo "$OUT" >&2
  exit 2
fi

要点は3つです。

  • 対象のパスをcaseで絞り、OpenAPI以外のファイルは即exit 0で通す
  • --rulesetを絶対パスで渡し、作業ディレクトリに左右されないようにする
  • 失敗したら標準エラー出力へlintの結果を出し、終了コード2で抜ける

最後の「終了コード2」が肝です。PostToolUseの時点でツールは実行済みなので、ブロックはできません。それでも終了コード2で終えると、標準エラー出力の内容がClaude Codeに見えます。終了コード0で終えたhookの標準エラー出力は、Claude Codeには見えません。lintの指摘をClaude Codeに直させたいなら、警告のみでも2で抜ける作りにします。

手元でスクリプトを直接動かして確かめました。警告が残るOpenAPIのパスを標準入力に渡すと終了コードは2、TypeScriptファイルのパスを渡すと0でした。

settings.jsonへの登録

プロジェクトの.claude/settings.jsonに登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/spectral-lint.sh"
          }
        ]
      }
    ]
  }
}

登録後に/hooksを開くと、PostToolUseの下にフックが表示されます。動作確認は、Claude CodeにoperationIdを消すような編集をさせ、次の応答でlintの指摘を踏まえた修正が走るかどうかを見るのが早いです。

lintの自動修正を言語別に組む考え方は、PostToolUse hookのLint自動修正の早見表にまとまっています。ESLintのように自動修正を使えるlinterと違い、SpectralのCLIオプションに自動修正はありません。そのため「指摘を返してClaude Codeに直させる」流れになります。

CLAUDE.mdにルールセットの在りかを書く

hookは検査を強制しますが、「なぜ指摘されたか」「どの規約が正か」はClaude Codeの文脈にも入れておくと、直し方が安定します。公式のメモリ機能は、CLAUDE.mdに具体的で検証できる指示を書くこと、長くなる内容は分けることを勧めています。

## OpenAPI定義の編集
 
- 定義は`openapi/`配下のYAMLが正。編集後はhookがSpectralを実行する
- 規約は`.spectral.yaml`が正。指摘が出たら、ルールを緩めずに定義側を直す
- 新しい規約を足すときは`.spectral.yaml`にルールを追加し、理由を1行で残す
- `operationId`は`lower-hyphen-case`にする

「ルールを緩めずに定義側を直す」の一行は実務で効きます。指示がないと、指摘を消す近道として.spectral.yamlのseverityをoffにする修正が混じりえます。ルールセットを触ってよい場面を、CLAUDE.mdで先に区切っておきます。

OpenAPI以外のコードが多いリポジトリでは、パス指定のルールに分けると文脈が節約できます。.claude/rules/openapi.mdの先頭にpathsを書くと、一致するファイルをRead、Write、Editしたときだけ読み込まれます。

---
paths:
  - "openapi/**/*.yaml"
---
 
# OpenAPI編集のルール
 
- 指摘は`.spectral.yaml`に従って直す
- ルールの重大度を下げて指摘を消さない

CLAUDE.mdは1ファイル200行以内を目安にするよう、メモリのドキュメントは書いています。OpenAPIの規約が長くなるなら、このパス指定ルールに移すのが筋です。

つまずきやすい点

運用に入ってから出やすい指摘を、原因と対処で並べます。

症状原因対処
lintしても何も出ない原因ルールセットが見つからない対処--rulesetを明示する
警告が多いのにhookが止まらない原因既定はerrorだけが失敗扱い対処--fail-severity warnを付ける
oas3-unused-componentが誤検出する原因他の仕様から参照される部品集の仕様では、使われていない扱いになる対処該当ルールをwarnかoffにする
外部ファイルへの$refが解決されない原因参照解決は別の設定が要る対処--resolverで解決方法を渡す

oas3-unused-componentは、ルールの説明自体が「仕様を検査すると誤検出がありえる」と警告しています。共通部品を別ファイルに切り出している構成では、特に出やすいルールです。$refの解決は、独自のresolverをJavaScriptで渡す仕組みがCLIに用意されています。

出力形式を変えたいときは、-fで選びます。json、stylish、junit、html、sarif、github-actionsなどが使えます。複数の形式を同時に出す場合は、形式ごとに-oで出力先を指定します。Claude Codeに指摘を読ませるだけなら、既定のstylishで十分です。

CIでも同じルールを走らせる

hookはローカルのClaude Codeにしか効きません。人の手で直した変更や、別のツールが書いた変更は、CIで同じ.spectral.yamlを通します。

npx spectral lint "openapi/**/*.yaml" \
  --fail-severity warn -f github-actions

github-actions形式はSpectral 6.10.0で導入されたもので、それより古いバージョンでは使えません。hookとCIで同じルールセットと同じ--fail-severityを使えば、手元で通ったものがCIで落ちる食い違いを避けられます。

OpenAPIを整えたあとの使い道としては、仕様からMCPサーバーを生成する方法があります。生成ツールは仕様の品質をそのままツールの品質にするため、lintでoperationIdや説明文が揃った定義は相性が良い入力です。手順はOpenAPI仕様からMCPサーバーを自動生成する3つの方法で扱っています。

まとめ

要点は、lintの指摘をClaude Codeに返す経路を作ることです。終了コード2で抜けるhookと、警告を失敗扱いにする--fail-severity warn、ルールの緩和を禁じるCLAUDE.mdの一行がそろえば、OpenAPIの編集は規約の中に収まりやすくなります。まずは既存の定義にspectral:oasだけをかけて指摘の量を見て、効かせたいルールだけをerrorに上げるところから始めるのが無理のない順序です。

この記事を共有:XはてブLinkedIn