Claude Media
Claude Codeでoasdiffを回し破壊的変更を検出 — 呼び出し側の影響を洗う

Claude Codeでoasdiffを回し破壊的変更を検出 — 呼び出し側の影響を洗う

OpenAPI定義の旧版と新版をoasdiffで比べ、削除・必須化・型変更ごとにクライアント側の修正箇所をClaude Codeに洗わせる手順を、許可ルールとhookの設定まで書きます。

OpenAPI定義の変更が既存クライアントを壊すかどうかは、oasdiffのbreakingコマンドが機械的に判定します。Claude Codeには、その結果を読ませて「呼び出し側のどこを直すか」を拾わせる役を任せます。定義の比較と影響調査を分けると、判定のぶれを抑えたまま、クライアントのコードベース全体を調べる作業だけをClaude Codeに寄せられます。

この記事では、旧版と新版の比較コマンド、変更の種類ごとの調べ方、実行コマンドの許可の切り方、編集のたびに検出するhookの順に進めます。lintによる規約チェックはSpectralでOpenAPIをlintする記事が扱っており、ここは互換性の検査に絞ります。

oasdiffは何を「破壊的」と判定するのか

oasdiffは、OpenAPI定義を比較するコマンドラインツールです。主に使うのは次の2つです。

  • oasdiff breaking: 既存クライアントを壊す変更だけを表示する
  • oasdiff changelog: 破壊的かどうかを問わず、利用者に影響しうる変更を表示する。説明文や例の編集は含まれない

検出する変更には3段階のレベルがあります。ERRは確実に壊れる変更、WARNは壊れるかもしれないが定義だけでは確定できない変更、INFOは壊れない変更です。breakingが表示するのはERRとWARNだけで、changelogは既定でINFOも含めます。

判定の基準は、サーバーの実際の挙動ではなく、定義が宣言する契約です。サーバーが寛容で、必須になったプロパティを省いたリクエストを黙って受け付けていても、定義上は無効なリクエストになるので破壊的と判定されます。ゲートウェイやバリデーター、生成されたクライアントSDKは契約どおりに厳密に動くためです。必須化の際にデフォルト値を付けても、省略したリクエストが有効になるわけではありません。

検出できる項目は数百あり、oasdiff checks changelogで一覧を出せます。特定の項目だけ重要度を変えたいときは、--severity-levelsに項目IDとレベルを書いたファイルを渡します。

旧版と新版を比べるコマンド

比較対象は、ファイルのパス、URL、gitのリビジョンのどれでも指定できます。リビジョンは<ref>:<path>の形で書き、内部でgit showが呼ばれます。仕様を一時ファイルに書き出す手間がなく、リポジトリ内で実行する必要があります。

# 作業ツリーの定義を、origin/mainの定義と比べる
oasdiff breaking origin/main:openapi.yaml openapi.yaml
 
# 破壊的変更があれば終了コード1にする(CIやhookで使う)
oasdiff breaking origin/main:openapi.yaml openapi.yaml --fail-on ERR
 
# リリースタグ同士を比べる
oasdiff changelog v1.0.0:openapi.yaml v2.0.0:openapi.yaml

終了コードは--fail-onで決まります。ERRならエラー級の変更があるとき、WARNなら警告級を含めて、INFOなら何か変更があれば1を返します。

インストールはbrew install oasdiff、go install github.com/oasdiff/oasdiff@latest、またはinstall.shによるバイナリ配置から選べます。Dockerイメージ(tufin/oasdiff)を使えば、インストールなしでも試せます。

Claudeに渡す結果は、既定のテキスト形式でも読めますが、--formatでjsonやyamlを選ぶと、変更ごとのフィールドを機械的に拾えます。singlelineは1変更1行で出力され、無視リストを作るときにも使えます。oasdiff schemaを実行すると、json出力のJSON Schemaを確認できます。

変更の種類ごとに、呼び出し側のどこを探すか

oasdiffの出力は「定義のどこが変わったか」を示すだけで、クライアントのコードのどこが壊れるかは教えてくれません。ここがClaude Codeの出番です。変更の種類ごとに、検索の足がかりが違います。

変更の種類壊れる理由クライアント側の探し方
エンドポイントの削除壊れる理由呼び出しが404などで失敗するクライアント側の探し方パス文字列、operationId、生成クライアントのメソッド名で検索
リクエストプロパティの必須化壊れる理由省略した呼び出しが無効になるクライアント側の探し方リクエスト本文を組み立てる箇所を探し、そのプロパティを渡しているか確認
型の変更壊れる理由送る値・読む値の型が合わなくなるクライアント側の探し方プロパティ名で検索し、型注釈とパース処理を確認
レスポンスプロパティの削除壊れる理由読み取り側がundefinedやnullを掴むクライアント側の探し方プロパティ名の参照箇所と、そのフォールバック処理を確認
enum値の削除壊れる理由送っていた値が無効になるクライアント側の探し方値のリテラルを検索

oasdiff checks changelogの出力をJSONにすると、各項目はarea(定義のどの部分か)、kind(存在・必須性・型・制約・値など)、direction(リクエストかレスポンスか)で分類されています。上の表の行は、この分類に沿った見方です。Claudeに「変更をkindとdirectionでグループ化してから探す」と指示すると、調べる順番が安定します。

次のようなプロンプトで、検出結果を渡して調査を依頼します。

oasdiff breaking origin/main:openapi.yaml openapi.yaml -f json の結果を読み、
破壊的変更ごとに次を表にして。
1. 変更の内容(パス・メソッド・プロパティ名)
2. src/ と tests/ の中で影響を受けそうな箇所(ファイル:行)
3. 修正方針(コードは変えず、方針だけ)
該当箇所が見つからない変更は「呼び出し側に影響なし」と明記して。

「コードは変えず」と添えるのが肝心です。検出と影響調査を終えてから、修正に進むかを人が判断する流れにすると、定義側の変更自体を見直す余地が残ります。破壊的変更を避ける選択肢として、削除の前に非推奨(deprecated)にする運用もoasdiff側が用意しています。その場合の扱いはDEPRECATION.mdにまとめられています。

プロジェクトの手順としてCLAUDE.mdに書く

毎回プロンプトを書く代わりに、手順をCLAUDE.mdに置いておくと、依頼が短くなります。

## OpenAPIの互換性確認
 
- 定義は `openapi.yaml`。比較は `oasdiff breaking origin/main:openapi.yaml openapi.yaml -f json`
- 破壊的変更が出たら、変更ごとに呼び出し側(`src/`, `tests/`)を検索して影響箇所を表にする
- 検索結果に基づいて報告し、コードの修正は指示があるまで行わない
- `--open` は使わない

実行コマンドの許可を切る

oasdiffは読み取り専用の検査コマンドですが、Claude Codeの既定では実行のたびに確認が出ます。確認を減らしつつ--openは止めるには、.claude/settings.jsonに許可ルールと拒否ルールを書きます。

{
  "permissions": {
    "allow": [
      "Bash(oasdiff breaking *)",
      "Bash(oasdiff changelog *)",
      "Bash(oasdiff checks *)"
    ],
    "deny": [
      "Bash(oasdiff * --open)",
      "Bash(oasdiff * --open *)"
    ]
  }
}

ルールの評価順は「拒否、確認、許可」で、最初に合致したものが結果を決めます。許可ルールの側に--openを含むコマンドが入っても、拒否ルールが先に効きます。逆に、広い拒否ルールの内側に狭い許可ルールで例外を作ることはできません。

末尾の*の前には空白が必要です。Bash(oasdiff breaking *)はoasdiff breaking単体にも合致します。Bash(oasdiff*)のように空白なしで書くと、oasdiffで始まる別の名前のコマンドにも合致します。サブコマンドの後ろに*を置くのは、許可の範囲をサブコマンドで絞るためです。

非対話で回す場合は、claude -pに--allowedToolsを渡す方法もあります。

claude -p "oasdiffの破壊的変更を読み、呼び出し側の影響箇所を表にして" \
  --allowedTools "Bash(oasdiff breaking *),Read,Grep"

編集のたびに検出するhookを置く

定義ファイルをClaude Codeに編集させるなら、編集の直後にoasdiffを走らせるPostToolUseのhookが使えます。PostToolUseはツールの実行後に呼ばれるので、変更を止めることはできません。ただし終了コード2で終わり、理由を標準エラー出力に書くと、Claudeにその内容が渡されます。Claudeは次の動きで修正や報告に入れます。

.claude/settings.jsonには次のように書きます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-openapi-breaking.sh"
          }
        ]
      }
    ]
  }
}

スクリプトは、標準入力のJSONから編集されたファイルのパスを取り出し、openapi.yamlのときだけ比較します。

#!/bin/bash
# .claude/hooks/check-openapi-breaking.sh
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
 
case "$FILE" in
  */openapi.yaml) ;;
  *) exit 0 ;;
esac
 
cd "$CLAUDE_PROJECT_DIR" || exit 0
REPORT=$(oasdiff breaking origin/main:openapi.yaml openapi.yaml --fail-on ERR 2>&1)
if [ $? -ne 0 ]; then
  echo "$REPORT" >&2
  exit 2
fi

hookの入力はJSONで標準入力に渡され、jqで取り出す書き方は、フォーマッターを走らせるhookの例と同じです。jqが入っていない環境では、先にインストールします。スクリプトには実行権限(chmod +x)も必要です。

hook全般の仕組みはClaude Code Hooks完全ガイドに、権限ルールの管理は/permissionsコマンドの記事にあります。

判定の基準をチームの方針に合わせる

既定の重要度がチームの互換性方針と合わないときは、--severity-levelsで項目ごとに変えられます。たとえばapi-security-scope-removedは既定がINFOですが、ERRに引き上げたければ、次の1行を書いたファイルを渡します。

api-security-scope-removed    err

指定できるレベルはerr、warn、info、none(無効化)の4つです。ここで重要度を決めておけば、hookの--fail-on ERRと、Claudeに読ませる結果の両方が同じ基準になります。

CIでも同じコマンドが使えます。--formatにはgithubactions(GitHub向け)とjunit(GitLab向け)があり、PRの画面に結果を出せます。Claude Codeの手元の検査とCIの検査を同じコマンドにそろえておくと、手元で通ったのにCIで落ちる、という食い違いが起きにくくなります。

隣接するツールとの使い分け

互換性の検査は、対象の形式ごとに専用のツールがあります。protobufならbuf breaking、OpenAPIの規約違反ならSpectralです。oasdiffはOpenAPI定義の差分に絞った道具で、規約チェックとは役割が重なりません。定義から生成したコードの扱いはOpenAPI Generatorの記事に書いています。

oasdiffには、ホスト型のMCPサーバー(https://api.oasdiff.com/mcp)もあります。claude mcp add --transport http oasdiff https://api.oasdiff.com/mcpで追加でき、インストールもAPIキーも不要です。ただし、仕様はClaudeがテキストとして渡す形になり、外部の$refは解決されません。分割した仕様を比べるなら、自己完結した1ファイルにしておく必要があります。手元のCLIをBashで呼ぶ構成のほうが、gitのリビジョン指定や複数ファイルの$refを扱えるぶん、リポジトリ内の運用には素直です。

つまずきやすい点

  • リビジョンが取れない: origin/mainがない浅いクローンではgit showが失敗します。oasdiffは取得のためのコマンドを表示し、--fetchでも取得できます。
  • allOfの比較がWARNになる: allOfを含む変更は、ブランチを1つずつ比べるため確定できずWARNで出ることがあります。--flatten-allofを付けると、統合して厳密に比べられます。
  • 読み取り専用プロパティの制約追加がINFOになる: readOnlyのプロパティはリクエストに現れないため、制約を足しても壊れません。重要度を自分で設定した項目は、この自動の引き下げが無効になります。
  • 未対応の範囲がある: 既知の制限として、contextを使ったリクエストパラメーターの検査と、callbackの検査はありません。

まとめ

oasdiffで契約の差分を確定させ、呼び出し側の洗い出しだけをClaude Codeに任せる分担にすると、定義の判定が揺れず、クライアントのコードを横断する調査も短く済みます。まずallowとdenyのルールを置き、CLAUDE.mdに手順を書いて手動で回し、運用が固まってからhookに載せる順が無理がありません。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. github.com/oasdiff/oasdiff/blob/main/docs/BREAKING-CHANGES.md
  2. github.com/oasdiff/oasdiff/blob/main/docs/GIT-REVISION.md
  3. github.com/oasdiff/oasdiff/blob/main/docs/CHECKS.md
  4. github.com/oasdiff/oasdiff/blob/main/docs/README.md
  5. github.com/oasdiff/oasdiff/blob/main/docs/MCP.md
  6. code.claude.com/docs/en/hooks-guide
  7. code.claude.com/docs/en/permissions
この記事を共有:XはてブLinkedIn