Claude Media
Claude CodeでOpenAPI Generatorを使う — 生成コードは直させず再生成させる設定

Claude CodeでOpenAPI Generatorを使う — 生成コードは直させず再生成させる設定

OpenAPI Generatorのクライアント生成を、生成物をClaude Codeに手で直させず仕様を直して再生成させる形に固定します。生成コマンドの固定、editのdeny、CLAUDE.mdの書き方を手順で示します。

OpenAPI Generatorで作ったクライアントコードは、Claude Codeに直接編集させない運用が安全です。生成物のバグに見えるものは、たいてい仕様かジェネレーターの設定に原因があり、生成物を手で直しても次の再生成で消えます。直す場所を仕様側に固定し、再生成までを決まったコマンドにしておけば、Claude Codeは「仕様を直して再生成する」だけで修正を終えられます。

この記事では、生成コマンドのスクリプト化、生成先へのEditのdeny、手書きファイルを守る.openapi-generator-ignore、CLAUDE.mdに書く規約を順に組みます。仕様そのものの検証はSpectralでOpenAPIをlintする構成が担当し、ここは「検証済みの仕様からコードを作る段」です。

生成コードを手で直させると何が起きるか

OpenAPI Generatorのgenerateは、仕様を読んでテンプレートからファイルを書き出します。出力先に同名のファイルがあれば、既定では上書きされます。Claude Codeが生成物のコンパイルエラーを見て型定義を手で直したとします。その場では動きますが、次に誰かが仕様を変えて再生成した瞬間に修正は消えます。

起きやすい流れは次のとおりです。

  • 生成クライアントの型が合わず、Claude Codeが生成ファイルを直接書き換える
  • 仕様側は直っていないので、再生成すると同じエラーが戻る
  • 手直しが消えたことに気づかず、レビューでも差分に出てこない

対処は、直す場所を3層に分けて固定することです。

層置き場所直す人
仕様置き場所openapi/openapi.yaml直す人Claude Code(編集可)
ジェネレーター設定置き場所openapi-generator.yamlと生成スクリプト直す人Claude Code(編集可、変更はレビュー対象)
生成物置き場所src/generated/直す人誰も手で直さない(Claude Codeにはdenyで禁止)

generateの最小コマンドと、先に決める3点

generateの必須指定は3つです。ジェネレーター名の-g、入力仕様の-i、出力先の-oです。ここで見落としやすいのが-oで、省略すると出力先は現在のディレクトリになります。リポジトリのルートで-oを付け忘れると、生成ファイルがルートに散らばります。

OpenAPI GeneratorはJava製のツールです。npmのラッパーは、そのJARの上でOSごとのオプション差を吸収するCLIとして用意されています。プロジェクトに閉じて入れるなら、開発依存にします。

npm install @openapitools/openapi-generator-cli -D
npx openapi-generator-cli version
npx openapi-generator-cli list

listで使えるジェネレーター名が分かります。ジェネレーターごとの固有オプションはconfig-help -g <名前>で確認します。JARを直接使う場合は、Java 11以上のランタイムが最低条件です。Macではjava -versionで入っている版を確かめます。

最小の生成は次の形です。

npx openapi-generator-cli generate \
  -i openapi/openapi.yaml \
  -g typescript-fetch \
  -o src/generated/api

typescript-fetchは例です。使う言語に合わせて-gを替えます。

生成コマンドを1つのスクリプトに固定する

Claude Codeに毎回オプションを組み立てさせると、-oの指定漏れや、オプションの揺れが起きます。コマンドはpackage.jsonのスクリプトに1本化し、Claude Codeにはそれだけを実行させます。

ジェネレーター固有の設定は、--additional-propertiesにカンマ区切りで並べる代わりに、設定ファイルへ出せます。-cにはJSONかYAMLを渡せます。拡張子で判別されるので、YAMLなら.ymlか.yamlにします。

# openapi-generator.yaml
npmName: "@example/petstore-client"
npmVersion: "1.0.0"
supportsES6: true

package.jsonには次のスクリプトを置きます。

{
  "scripts": {
    "gen:api": "openapi-generator-cli generate -i openapi/openapi.yaml -g typescript-fetch -o src/generated/api -c openapi-generator.yaml --minimal-update",
    "gen:api:dry": "openapi-generator-cli generate -i openapi/openapi.yaml -g typescript-fetch -o src/generated/api -c openapi-generator.yaml --dry-run"
  }
}

2つのフラグは再生成の確認に効きます。--dry-runは、実際には書き込まずに「どのファイルがどう変わるか」を報告します。--minimal-updateは、内容が変わったファイルだけを書き出します。後者を付けておくと、仕様を1か所直したときのgit差分が、本当に変わったファイルだけに絞れます。

使わせたくないのは--skip-validate-specです。これは仕様の検証をスキップするフラグで、仕様のエラーを生成物側の不具合として扱わせる近道になります。スクリプトには入れず、Claude Codeが足そうとしたら止める対象にします。

手書きファイルを.openapi-generator-ignoreで守る

生成先に、手で書きたいファイル(READMEや補助のユーティリティ)が混ざることがあります。--skip-overwriteでも上書きは避けられますが、公式は個別のファイルやディレクトリを指定できる.openapi-generator-ignoreのほうが細かく制御できると説明しています。書式は.gitignoreに近く、否定パターンも使えます。

# 生成しても上書きしない手書きファイル
README.md
src/runtime-extras.ts
 
# docs 配下は生成しない。ただし UserApi.md だけは出す
docs/**
!docs/UserApi.md

置き場所には決まりがあります。.openapi-generator-ignoreは出力先ディレクトリの直下に置きます。初回の生成時は、--ignore-file-override=/path/to/ignore_fileで別の場所のファイルを指定できます。ただしこれは完全な上書きで、出力先にあるignoreファイルを置き換えます。

生成先をdenyで固める

CLAUDE.mdだけでは強制になりません。公式は、CLAUDE.mdを「文脈であって強制される設定ではない」と位置づけ、技術的に強制するのは設定側と説明しています。生成物の編集を止めるのは、.claude/settings.jsonの権限ルールです。

{
  "permissions": {
    "deny": [
      "Edit(/src/generated/**)",
      "Bash(npx openapi-generator-cli *)",
      "Bash(openapi-generator-cli *)"
    ],
    "allow": [
      "Bash(npm run gen:api)",
      "Bash(npm run gen:api:dry)"
    ]
  }
}

ポイントは3つあります。

  • Editでdenyする: 読み取りまで禁じるReadのdenyは、同じパスへのEditとWriteもブロックします。生成コードを読ませたい場面は多いので、書き込み側だけをEditで止めます。なおWriteにパスを付けたルールは一致せず、Editのルールが書き込みも含めて見る仕様です。
  • 先頭のスラッシュで起点を固定する: プロジェクト設定では/src/generated/**がプロジェクトのルートからのsrc/generated/を指します。
  • ジェネレーター本体のコマンドを直接叩かせない: Bash(npx openapi-generator-cli *)のdenyで、-oを付け忘れた生のコマンドを止めます。許可するのはスクリプトの2本だけです。ルールは「deny、ask、allow」の順に評価され、denyで一致したものはallowで取り消せません。そのため、スクリプトの中で呼ばれるopenapi-generator-cliはこのdenyに掛からず、スクリプト名のallowだけが効きます。

denyが効く範囲と効かない範囲

ここは誤解しやすい点です。Editのdenyは、Claude Codeの組み込みのファイルツールと、catやsedやteeのような認識されたファイルコマンド、>などのリダイレクトの対象に適用されます。一方で、名前を出さずにファイルを書くコマンドや、PythonやNodeのスクリプトが自分でファイルを開いて書く場合には適用されません。

この仕様は今回の運用に都合よく働きます。許可したnpm run gen:apiの中で動くジェネレーターは別プロセスなので、denyの対象外として生成先に書き込めます。つまり「Claude Codeは生成先を直接書けないが、決まったスクリプト経由なら再生成できる」状態になります。

反対に、Claude Codeが生成先を書き換えるPythonスクリプトを作って実行すれば、denyをすり抜けます。公式は、すべてのプロセスからのアクセスをOSレベルで止めたい場合はサンドボックスを有効にするよう案内しています。強い制約が要る環境では、denyとサンドボックスを併用します。

CLAUDE.mdに書く規約

denyが「止める」役なら、CLAUDE.mdは「理由と次の手順を伝える」役です。拒否されたClaude Codeが、迷わず正しい手順に戻れるように書きます。

## OpenAPI からのコード生成
 
- `src/generated/` は OpenAPI Generator の出力。直接編集しない(編集はdenyされる)
- 生成物に不具合があるときは、次の順で直す
  1. `openapi/openapi.yaml` の定義を直す
  2. 直らなければ `openapi-generator.yaml` の設定か `.openapi-generator-ignore` を直す
  3. `npm run gen:api:dry` で変わるファイルを確認してから `npm run gen:api` で再生成する
- `openapi-generator-cli` を直接実行しない。必ず `npm run gen:api` を使う
- `--skip-validate-spec` は付けない。仕様のエラーは仕様側で直す
- 手書きのコードは `src/client-extras/` に置く

公式は、CLAUDE.mdを「検証できるほど具体的に書く」ことを勧めています。「生成物は大事にする」のような曖昧な文より、ファイルパスとコマンド名を書いたほうが守られやすくなります。

仕様を直したら自動で再生成させる(任意)

仕様ファイルを編集するたびにnpm run gen:apiを回したいときは、PostToolUseのhookが使えます。PostToolUseはツール呼び出しが成功した後に動くイベントです。

#!/bin/bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
  */openapi/*.yaml|*/openapi/*.yml) ;;
  *) exit 0 ;;
esac
OUT=$(cd "$CLAUDE_PROJECT_DIR" && npm run gen:api 2>&1)
if [ $? -ne 0 ]; then
  echo "$OUT" >&2
  exit 2
fi

.claude/settings.jsonには、PostToolUseのmatcherにEdit|Writeを指定してこのスクリプトを登録します。失敗したときだけ終了コード2で抜けるのが要点です。終了コード0で終わったhookの標準エラー出力はClaude Codeに見えず、PostToolUseから警告を渡すには終了コード2を使うと公式が説明しています。生成が失敗した理由が次の応答に載るので、仕様の誤りをその場で直せます。

lintとの順序は、先にSpectralで仕様を検証し、通った仕様だけを生成に回すと、エラーが「仕様の問題」と「生成の問題」に分かれて読みやすくなります。仕様からコードではなくMCPサーバーを作りたい場合は、OpenAPIからMCPサーバーを自動生成する方法が別の選択肢です。

テンプレートや型を変えたいとき

「生成物の見た目を変えたい」要望は、生成物の編集ではなくジェネレーター側のカスタマイズで受けます。

  • 型の置き換え: --type-mappingsで仕様の型を生成先の型に替え、必要な場合は--import-mappingsで取り込む型を指定します。公式の例は、Kotlinサーバー生成でDateTimeをjava.time.LocalDateTimeへ替えるものです。両方を組み合わせるのが基本です。
  • 既存のモデルを再利用: 既に手元にあるクラスを使いたいときは、--language-specific-primitivesで「生成しない型」として扱い、--import-mappingsでインポート元を教えます。
  • テンプレートの変更: author template -g <ジェネレーター名>でテンプレートをローカルに取り出し、-tで自作のテンプレートのフォルダーを指します。出力先の既定はoutです。

テンプレートを取り出すと、組み込みと同名のファイルは自作が優先されます。ただし公式は、名前を少し変えたテンプレートを足すと、ファイルが重複したり、後から処理されたほうが勝ったりして挙動が未定義になると注意しています。変更したいテンプレートは、元の名前のままで置き換えます。

これらはどれも設定ファイルとテンプレートの変更なので、openapi-generator.yamlと同じ「直してよい層」に属します。変更後はgen:api:dryで差分を見てから再生成させます。

つまずきやすい点

症状原因対処
ルートにファイルが散らばった原因-oを省略した(既定は現在のディレクトリ)対処スクリプト経由に限り、直接実行をdenyする
再生成で手書きが消えた原因生成先に手書きを置いた対処手書きは生成先の外へ。残すなら.openapi-generator-ignore
毎回、全ファイルが差分になる原因生成のたびに全ファイルを書き直している対処--minimal-updateを付ける
ignoreファイルが効かない原因出力先ディレクトリの直下に無い対処出力先の直下に置く
denyしたのに生成先が書き換わる原因Pythonなどのスクリプトが直接書いた対処サンドボックスでOSレベルに制限する

まとめ

生成コードに手を入れさせないための仕掛けは、3つの層で足ります。コマンドをnpm run gen:apiに1本化して-oの指定漏れを消し、生成先はEditのdenyで止め、CLAUDE.mdには「どの順で直すか」を具体的に書きます。denyが組み込みのファイルツールと認識されたファイルコマンドにしか効かない点だけは、サンドボックスで補う判断になります。

導入は小さく始められます。まずgen:apiとgen:api:dryを作り、生成先のdenyを入れ、次に手書きを外へ出す。この順なら、既存のプロジェクトでも1回の整理で終わります。コマンドを決まった入口に絞る同じ型の例として、uvプロジェクトでpip直叩きを禁じる設定があります。

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