Claude Media
Claude Codeとgraphql-inspectorで、スキーマの破壊的変更をPRで止める

Claude Codeとgraphql-inspectorで、スキーマの破壊的変更をPRで止める

graphql-inspectorのdiffがBreakingと判定したPRを止め、@claudeにフィールド削除を@deprecated経由へ直させる流れを、GitHub Actionの設定とCLAUDE.mdの規約で組みます。

GraphQLのスキーマからフィールドを消すと、そのフィールドを使っているクライアントが壊れます。Claude Codeにスキーマを触らせる場合、型エラーを消す一番近道は「邪魔なフィールドを削除する」ことなので、この事故は起きやすい部類です。

graphql-inspectorは、旧スキーマと新スキーマを比べて、変更を破壊的(Breaking)・危険(Dangerous)・安全(Safe)に分類するツールです。Breakingが1件でもあれば、CLIのプロセスは失敗で終わります。これをGitHub ActionでPRの必須チェックにすれば、削除を含む変更はマージ前に止まります。

この記事では、止まったPRをClaude Codeに直させるところまでを組みます。直し方の方針は「削除せず、まず@deprecatedを付ける」です。

破壊的変更の判定はgraphql-inspector、直す役はClaude

役割は2つに分けます。判定は決定論的なツールに任せ、Claudeには判定結果を材料にした修正だけをさせます。

構成

役割の分担

  • graphql-inspector

    旧スキーマと新スキーマを比べ、変更を3種類に分類します。Breakingがあればチェックを失敗させます。

  • Claude Code GitHub Action

    PRコメントの@claudeを受けて、失敗の中身を読み、スキーマを直してpushします。

  • CLAUDE.md

    「フィールドは削除せず@deprecatedを付ける」という規約を、Claudeの修正方針として固定します。

スキーマから型を生成する側の設定は、GraphQL Codegenの生成物を触らせないdenyとhook設定が扱っています。こちらは生成物の保護で、本記事は「スキーマそのものの互換性」を検査する別の層です。

diffコマンドが出す3分類を確認する

CLIは@graphql-inspector/cliとgraphqlをグローバルに入れて使います。インストール手順では、graphqlはpeer dependencyとして合わせて入れる形で案内されています。

npm i --global @graphql-inspector/cli graphql
graphql-inspector diff OLD_SCHEMA NEW_SCHEMA

OLD_SCHEMAとNEW_SCHEMAには、ファイル、URL、github:user/repo#master:schema.graphqlのような参照を渡せます。PRの検査では、基準ブランチ側のスキーマが旧、手元のファイルが新です。

公式サイトにある出力例を見ると、分類の感覚がつかめます。次のスキーマの組が例として載っています。

# 旧
type Post {
  id: ID!
  title: String
  createdAt: String
  modifiedAt: String
}
type Query {
  post: Post!
  posts: [Post!]
}
 
# 新
type Post {
  id: ID!
  title: String!
  createdAt: String
}
type Query {
  post: Post!
}

この組に対するdiffは、3件の変更を検出します。Post.modifiedAtの削除とQuery.postsの削除がBreaking、Post.titleがStringからString!になる変更がSafeです。フィールドの削除は、使っているクライアントがあれば確実に壊れるためBreakingになります。

出力の読み方は、次の3点で足ります。

  • Breaking: クライアントが壊れうる変更。1件でもあれば失敗
  • Dangerous: 壊れるとは限らないが注意が要る変更
  • Safe: 互換性を保つ変更

@deprecatedを経由させるルールを知っておく

ここで押さえたいのが、@deprecatedが付いたフィールドの扱いです。公式のルール説明では、非推奨フィールドの削除も、既定ではBreakingとして数えられます。suppressRemovalOfDeprecatedFieldルールを有効にすると、それがDangerousに下がり、チェックを失敗させなくなります。

つまり、スキーマ変更は2回のPRに分かれます。

手順

フィールドを消すまでの2段階

  1. 1

    PR1: @deprecatedを付ける

    フィールドは残したまま、@deprecated(reason: "...")を付けます。クライアントが移行する猶予が生まれます。

  2. 2

    PR2: 移行後に削除する

    suppressRemovalOfDeprecatedFieldを有効にしたチェックでは、非推奨フィールドの削除はDangerous扱いになり、チェックが通ります。

ルールを有効にしないチェックのままだと、PR2も止まります。非推奨を経由した削除だけを通したいなら、このルールがその合図になります。CLIでは--ruleで指定します。

graphql-inspector diff \
  "git:origin/main:./schema.graphql" schema.graphql \
  --rule suppressRemovalOfDeprecatedField

git:origin/master:./schema.graphqlの形は、CI用の案内ページに載っている書き方です。ブランチ名は自分のリポジトリの基準ブランチに合わせます。

@deprecatedを付けた時点の変更が、手元の環境でどの分類になるかは、公式ページに例がありません。PR1の想定結果を覚えるより、一度手元で旧新の組を作ってdiffを流す方が確実です。

PRの必須チェックにするワークフロー

GitHub Action側は、schema入力に基準ブランチのスキーマをブランチ名:ファイルパスの形で渡します。

name: Schema Check
on:
  pull_request:
jobs:
  inspector:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      checks: write
    steps:
      - uses: actions/checkout@v6
      - uses: graphql-hive/graphql-inspector@master
        with:
          schema: 'main:schema.graphql'
          rules: |
            suppressRemovalOfDeprecatedField
          annotation-level: breaking

公式の例はuses: graphql-hive/graphql-inspector@masterで書かれています。実際に使う際はタグ指定などをチームの方針に合わせます。checksの権限に書き込みが要る点は、公式が明記しています。

使える入力のうち、運用に効くものを挙げます。

入力役割既定値
schema役割旧スキーマの参照既定値なし
fail-on-breaking役割Breakingで失敗させる既定値true
approve-label役割破壊的変更を承認済みとして通すラベル既定値approved-breaking-change
annotation-level役割PR差分に出す注釈の最小重大度既定値all
rules役割組み込みルールや自作ルール既定値なし

approve-labelは、意図した破壊的変更を人が承認して通すための出口です。ここは人間が付ける前提にして、Claudeにラベルを付けさせない運用が安全です。

annotation-levelをbreakingにすると、インライン注釈がBreakingだけになります。チェックのサマリには全変更が出るので、情報は減りません。

出力にはbreaking-changes、dangerous-changes、safe-changes、changes(総数)があり、後続のステップで使えます。この出力を次節のClaudeへの依頼に渡す方法もあります。

止まったPRを@claudeに直させる

Claude Code GitHub Actionは、ワークフローにpromptが無いとき、コメントの@claudeを待つ対話モードで動きます。公式の例は、issue_commentとpull_request_review_commentで起動する形です。

name: Claude Code
on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
jobs:
  claude:
    if: contains(github.event.comment.body, '@claude')
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      issues: write
      id-token: write
      actions: read
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          claude_args: "--max-turns 8"

actions: readがあるので、Claudeはチェックの結果をPR上で読めます。--max-turnsは1回の実行の上限で、コスト面の保険になります。

Inspectorのチェックが失敗したら、PRにこう書きます。

@claude graphql-inspectorのチェックがBreakingで失敗しています。
削除したフィールドを元に戻し、代わりに @deprecated(reason: "...") を付けてください。
スキーマ以外のファイルは変更しないでください。

Claudeは同じブランチにコミットを積みます。チェックが再実行され、Breakingが消えれば通ります。ここで1つ落とし穴があります。github_tokenにsecrets.GITHUB_TOKENを渡すと、その名義のコミットではワークフローが動きません。公式のトラブルシューティングは、渡している場合は外してClaude GitHub Appとして動かすか、カスタムのアプリトークンを使うよう案内しています。外さないと、Claudeが直しても再検査が走らず、チェックは赤いままです。

もう1つ、@claudeを人が書く運用にしておくのが無難です。ボットのアクターは、allowed_botsに入れない限りActionが拒否します。Inspectorのチェック失敗を契機に別のボットが@claudeと書く構成は、この制約で止まります。

CLAUDE.mdに互換性の規約を書く

毎回コメントで方針を伝えるより、CLAUDE.mdに置く方が再現性があります。ClaudeはリポジトリのルートのCLAUDE.mdを読んで、PR作成時や依頼への対応時に従います。

## GraphQLスキーマの互換性
- `schema.graphql`のフィールド・引数・enum値は、削除も型の変更もしない
- 不要になったフィールドには `@deprecated(reason: "代替と移行先を書く")` を付けて残す
- 削除は、非推奨化から移行が済んだあとの別PRで行う
- スキーマを変更したら `graphql-inspector diff` を実行し、Breakingが0件であることを確認する
- 承認ラベル `approved-breaking-change` は付けない(人が付ける)

最後の2行が運用の要です。Claude自身がローカルでdiffを実行し、Breakingが0件になるまで直す反復が、CI待ちの往復を減らします。CLAUDE.mdを短く保つコツは、VitestのCLAUDE.mdの書き方にも通じます。公式の指針でも、CLAUDE.mdは毎回読まれるため簡潔さが勧められています。

運用で迷う分岐

Breakingを意図的に出したいとき

メジャー変更や、利用者がいないと分かっているフィールドの削除では、承認ラベルが出口です。fail-on-breaking: falseで全体を甘くするより、PR単位でラベルを付ける方が記録も残ります。

実際に使われているかで判定したいとき

considerUsageルールとonUsageに渡すスクリプトで、「直近30日で使われたか」のような独自判定を組めます。公式のサンプルは、使用実績を調べる関数をtype・field・argumentごとに呼ぶ形です。使用状況の取得先は自前で用意する必要があります。

破壊的変更の中身をClaudeに渡したいとき

Actionのbreaking-changes出力は、メッセージ文字列のリストです。たとえば「Field Xyz was removed from object type Abc」という形で出ます。これをプロンプトに差し込めば、@claudeのコメントを人が書かなくても依頼文を組めます。ただし自動化モードでは、Claudeが使えるツールを--allowedToolsで明示する必要があります。

GraphQL APIをClaudeに叩かせたい場合

スキーマの互換性検査とは別の話で、実行時にClaudeがGraphQLのAPIを呼ぶ構成はGraphQL MCPサーバーの使い方にあります。

設定の前に確認すること

  • 基準ブランチにschema.graphqlのようなファイルがあること。コードファーストの場合は、graphql-inspector introspectなどでスキーマファイルを出力する工程が要ります
  • Inspectorのチェックをブランチ保護の必須チェックに登録していること。登録しないと、Breakingが出てもマージできてしまいます
  • Claudeに触らせるのはスキーマと呼び出し側の修正までにし、承認ラベルの付与と本番デプロイは人間の操作に残すこと

GitHub Actionsのワークフロー自体をClaude Codeに書かせる手順は、ワークフローYAMLの作り方が詳しいです。

まとめ

判定をInspectorに、修正をClaudeに分けると、「消して型エラーを黙らせる」変更はPRの時点で赤になります。@deprecatedを挟む2段階の流れは、suppressRemovalOfDeprecatedFieldルールを有効にしたときだけ、PR2が通る形になります。承認ラベルだけは人の手に残してください。

参照した一次ソース

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

  1. code.claude.com/docs/en/github-actions
  2. the-guild.dev/graphql/inspector
  3. the-guild.dev/graphql/inspector/docs/commands/diff
  4. the-guild.dev/graphql/inspector/docs/products/action
この記事を共有:XはてブLinkedIn