runpod-migrate について
runpod-migrateは、RunpodのGraphQLやREST v1からREST v2への移行をエージェント向けに案内するスキルです。呼び出し箇所を調査し、破壊的変更や維持が必要な旧APIを把握できるよう支援します。
主な機能
- 01Runpod API呼び出しの調査と分類
- 02コントロールプレーンとServerless APIの区別
- 03フィールドとパスの変更確認
- 04v2で削除された機能の検出
- 05移行後の検証
活用シーン
- 01コードベース内の旧API呼び出しの把握
- 02REST v1またはGraphQL呼び出しのv2移行
- 03削除された機能への依存の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ runpod/ runpod-plugins-official --skill runpod-migrateインストール後、Claude Code で/runpod-migrateを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/availability-and-fallback.eval.mdinventory-before-editing.eval.mdrest-only-scope.eval.mdreference/breaking-changes.mdgraphql-to-v2.mdrest-v1-to-v2.mdrollback-flag.mdunlocks.mdworked-example.mdscripts/rp_api_inventory.pytests/test_rp_api_inventory.py詳細
Runpod REST v2 への移行
コードベースをGraphQL API(api.runpod.io/graphql)とREST v1
(rest.runpod.io/v1)からREST v2(api.runpod.io/v2)へ移行します。
得られるメリットを、それぞれ一行で — ステップ 6 で、実際のコードに合わせてユーザーに伝えてください。今は読み上げないでください。
- レンタル前に在庫を確認 —
GET /v2/catalog/gpus?include=AVAILABILITY&product=POD。v1 にはカタログがなかったため、容量の再試行ループは毎回手探りでした。 - エンドポイントが独自のジョブ URL を返す —
requestUrls.runにより、文字列を組み立てる必要がなくなります。 - 実際のライフサイクル状態 —
PROVISIONING/STARTING/ERRORとactionsリストにより、待機ループがタイムアウトする前にすぐ失敗できます。 - 誤りを明確に検出 — 不明なリクエストフィールドは名前を示して拒否され、構造化されたエラーと正確なステータスコードが返されます。
すべてのメリットは、コードが X を実行 → v2 では Y が利用可能という形式でまとめられています。 reference/unlocks.md — ステップ 6 で開いてください。
コードに手を入れる前に
対象範囲を推測して伝え、そのまま進めてください — 最初に質問を並べないでください。
| ユーザーの発言 | 対象範囲 |
|---|---|
| 「v2 に移行して」/具体的な指定なし | all — REST v1 と GraphQL |
| 「REST だけでいい」「GraphQL はそのままにして」 | rest — REST v1 のみ |
| 「GraphQL から移行して」 | graphql — GraphQL のみ |
この表であらゆる言い回しに対応できるため、対象範囲について作業を中断する必要はありません。どの行に当てはまるかを伝えて、続けてください。尋ねる必要がある質問は後で出てきます — ステップ 3 で、インベントリから、コードが v2 で削除された機能に依存していると分かったときです。これは実際に選択が必要な分岐であり、ユーザーに代わって決めることはできません。
v2 に相当する機能がなく、対象範囲にかかわらず GraphQL のままにする必要があるものもあります。アカウント/請求の ID 情報(myself)とスポット/中断可能な Pod です。「完全な」移行でも、これらの呼び出しは残ります。最後になってユーザーが気づくことのないよう、最初に伝えてください。
サーバーレスジョブ API は絶対に書き換えないでください。 https://api.runpod.ai/v2/<endpointId>/run、/runsync、/status、/stream、/cancel は、パスに v2 が含まれているだけの別の APIです。変更されておらず、対象範囲外です。インベントリでは別途報告されるため、手を加えないでください。
ワークフロー
1. インベントリ — 数えていないものは移行しない
スキャナーはこのファイルの隣、インストールされたスキルのディレクトリにあります。ユーザーのリポジトリにはありません。まずそのパスを特定してください。作業ディレクトリはユーザーのプロジェクトです。
# 1. Claude Code plugin installs expose the plugin root:
SCAN="$CLAUDE_PLUGIN_ROOT/skills/runpod-migrate/scripts/rp_api_inventory.py"
# 2. Otherwise substitute the directory you loaded this SKILL.md from — you know it:
[ -f "$SCAN" ] || SCAN="<directory containing this SKILL.md>/scripts/rp_api_inventory.py"
# 3. Last resort, search the usual install roots:
[ -f "$SCAN" ] || SCAN=$(find ~/.claude ~/.agents ~/.codex ~/.config -name rp_api_inventory.py 2>/dev/null | head -1)
python3 "$SCAN" --help >/dev/null || echo "scanner not found — resolve it before continuing"
次に、ユーザーのリポジトリのルートから実行します。
python3 "$SCAN" . > runpod-api-inventory.md
python3 "$SCAN" . --json > runpod-api-inventory.json # if you want to drive edits from it
python3 "$SCAN" . --scope rest # REST-only migrations
runpod-api-inventory.md はユーザーのリポジトリに作成されます。そのことを伝え、移行を引き渡す前に削除するか、gitignore に追加してください。
インストール不要の Python 標準ライブラリのみを使用します。世代別に分類したすべての呼び出し箇所を報告します。分類は、GraphQL、REST v1、v1/GraphQL のフィールド名、REST v2 の既存の呼び出し、サーバーレスジョブ API、SDK/CLI ラッパーです。また、ファイルごとの推奨作業順も示します。
何かを編集する前に、インベントリの表をユーザーに見せてください。 ユーザーは、自分のコードがどの API を使っているか把握していないことがよくあります。数か月前にエージェントがバージョンを選び、どこにも記録しなかった、ということもあります。「v1 を使うファイルが 3 つ、GraphQL が 2 つ、v2 が 1 つ、ジョブ API が 2 つ — ジョブ API には手を加えないでおく」という情報が、このスキル全体で最も役に立つことも少なくありません。
検出できること、できないこと
正規表現で行をスキャンしますが、分類によって実用的な結果が得られます。単純な
grep -r runpod では、次の2つを間違って扱ってしまいます。
api.runpod.ai/v2とapi.runpod.io/v2の違い。 1文字しか違いません。.ai はサーバーレスジョブ API なので、変更してはいけません。.io は移行先のコントロールプレーンです。v2を grep すると、実際には移行されていないコードベースを「すでに移行済み」と判定してしまいます。- どちらのバージョンでも有効な名前。
/podsは v1 と v2 の両方で使われるパスです。["pods"]は v2 のエンベロープ解除で使われます。idleTimeoutは v1 ではトップレベルにあり、v2 ではworkersの下にネストされています。同じ行に v2 のコンテキストがある場合、スキャナーはその検出を抑制するため、単語が出現するたびに報告するのではなく、残っている作業を報告します。
また、URL だけでなくフィールド名も探します。そのため、「runpod」という文字列が一度も出てこないファイルも見つけられます。たとえば、ラッパーの戻り値から p["costPerHr"] を読み取るモジュールには、URL もインポートも操作名もありません。それでも、v2 のリネームで確実にひそかに壊れます。
本当に解決できない盲点は4つあります。毎回、手作業で確認してください。
| 盲点 | 確認方法 |
|---|---|
ベース URL がコードではなく設定ファイルにある(settings.yaml、.env、ConfigMap、Terraform) | スキャナーはこれらのファイルも読み取るため、URL は見つかります。ただし、それを使う呼び出し箇所は別の場所にあります。その設定キーを読み取る箇所を grep してください。 |
ヘルパー関数で組み立てられるパス — _url("pods", pod_id, "stop") | 間接呼び出し箇所の可能性ありとして報告されます。ただし、resp.json()["pods"] と同じように見えるため、参考情報にすぎません。1つずつ確認してください。 |
SDK ラッパー(import runpod) | API の世代はコードではなく、インストールされたバージョンに依存します。requirements.txt/ロックファイルと、SDK 自身のリリースノートを確認してください。 |
| 生成されたクライアント | OpenAPI/GraphQL ドキュメントが実際の情報源です。生成済みファイルを編集するのではなく、v2 の仕様から再生成してください。 |
次に、スキャナーがフラグを立てたコードを読んでください。スキャナーが見つけるのは呼び出し箇所であり、ラッパーの動作を理解するわけではありません。呼び出し元をたどってください。costPerHr → cost のようにレスポンスフィールドが変更されると、リクエストビルダーだけでなく、すべての呼び出し元が壊れます。コードグラフや LSP インデックスがすでに利用可能なら、役立つのはこのステップです。
2. 破壊的変更を差分確認より前に説明する
reference/breaking-changes.md を読み、どの変更が実際にユーザーのコードに当てはまるかを伝えてください。変更は2種類あり、ユーザーが特に懸念しているのは2つ目です。
- リネームと移動 — 表面化しやすい変更です。v2 は不明なリクエストフィールドを
422で拒否し、フィールド名を列挙します。そのため、リネーム漏れが気付かれないまま本番環境に入り込むことはありません。 - 名前は同じでも意味が異なる変更 — 気付きにくく、テストがすべて成功しても安心できない理由です。リファレンスには該当する変更がすべて列挙されています。特に影響が大きいのは次の2つです。
flashbootはブール値から3値の列挙型になりました。また、v1 の/billing/endpoints(サーバーレスの利用額)は、v2 では/billing/serverlessです。v2 の/billing/endpointsは別の製品(パブリックエンドポイント)の請求を扱い、200とその製品の正確な合計を返しますが、呼び出し元が求めている製品とは異なります。
3. 計画を立て、必須作業と整理作業に分ける
編集を始める前に計画を書き出し、最終的なまとめまで、次の分類を分けたままにしてください。
- 必須作業 — これをしないと v2 では動作しません。
- 整理作業 — どちらでも動作しますが、v2 ではコードを削除できます(手書きのジョブ URL、手作りの可用性リトライ、SSE に置き換えられるポーリングループなど)。
- ユーザーが判断する必要があること — コードが v2 で完全に削除された機能に依存しています。スポット/中断可能なポッド、Savings Plans、配置制約(
countryCodes、minDownloadMbps、…)、ポッドのreset、ポッドごとの GPU フォールバックが該当します。breaking-changes.md の Class 3 を参照し、記憶に頼らず内容を確認してください。v2 の拡張に伴い、この分類から外れる項目もあります。CUDA ピン留め、templateId、CPU エンドポイントへの書き込みは、以前はここに含まれていましたが、現在は含まれません。
3つ目の分類に該当するコードを書く前に、必ず作業を止めてユーザーに確認してください。ただし、代替案も一緒に提示してください。再構築できる機能もあれば、本当に代替手段がない機能もあり、その違いによって確認する内容が変わります。再構築できる場合(countryCodes → カタログフィルター + dataCenterIds + 配置アサーション、ポッドごとの GPU フォールバック → 可用性順のループ)、その方法を示し、変更に影響する質問を1つだけしてください。countryCodes の場合は、その制限が優先事項だったのか、コンプライアンス要件だったのかを確認します。代替手段がない場合は、動作の変更を受け入れる、その呼び出しを v1/GraphQL に残す、または設計を見直す、という選択肢があり、選ぶのはユーザーです。
いずれの場合も、再構築できるのに機能の削除を行き止まりのように提示しないでください。そうすると、残す必要のなかった v1 の呼び出しをユーザーに残させることになります。また、フィールドを削除して # no v2 equivalent コメントを付けることも決してしないでください。それはこの分類が防ぐためにある失敗例です。インフラの動作が知らないうちに変わってしまいます。該当項目がなければ、その旨を伝えてください。安心につながり、1行で済みます。
4. 1つのコミットにつき1ファイルを移行する
スキャナーが提案する順序(呼び出し箇所が少ない順)で進めます。ただし、例外が1つあります。 複数の呼び出し箇所で同じトランスポートヘルパーを使っている場合は、呼び出し箇所の数に関係なく、先にヘルパーを移行してください。スキャナーは呼び出し箇所の数で順序を決めますが、インポートは認識できないため、クライアントをインポートするモジュールよりも先に、その利用側を並べてしまうことがあります。利用側を先に移行すると、変更しようとしているインターフェースを前提にコードを書くことになります。
ファイルごとに:
-
パスとフィールドの対応付けには、reference/rest-v1-to-v2.md または reference/graphql-to-v2.md を使います。フィールドが表にない場合や、API が表の内容と一致しない場合は、仕様を直接確認してください — 正確な情報源 — 自分で仕様を確認する。
-
ポッド上の
gpuTypeIdsとgpuTypePriorityは、フィールド名の変更ではなく、新しいコードを書いていることを意味します。 v2 のポッドで指定できる GPU タイプは1つなので、サーバー側のフォールバックは、カタログを順に確認するクライアント側のループになります。実装例はこちらです: breaking-changes.md → GPU フォールバックリストの置き換え。 エンドポイントにループは不要です —gpu.poolsはすでにリストになっており、ワーカーは空き容量のあるリスト内のプールで起動します。 -
カタログを読み取るときは、必ず
productとともに可用性をリクエストしてください。 この移行で追加するGET /v2/catalog/gpus、/catalog/cpus、または/catalog/datacentersには、include=AVAILABILITY(データセンターではGPU_AVAILABILITY/CPU_AVAILABILITY)を指定します。可用性は Runpod のすべてのユーザーが最初に気にする情報であり、呼び出しコストも同じです。v1 ではリクエストできなかったからといって、省略しないでください。include=AVAILABILITYだけでは400エラーになります。/catalog/gpusと/catalog/cpusでは、productはこれと併せて指定する必要があり、単独で指定しても無効です。どちらの場合も400エラーになります。意図的にデフォルト値はありません。同じ GPU でもPODでは不足し、SERVERLESSでは十分な場合があるため、コンテキストを明示する必要があります。作成する対象に合った値(POD、SERVERLESS、またはCLUSTER)を選んでください(CPU ではPODまたはSERVERLESSを指定します)。GET /v2/catalog/gpus?include=AVAILABILITY&product=POD -
v2 に慣れるまでは、ロールバックフラグ(
RUNPOD_API_V1=1)も用意してください: reference/rollback-flag.md。本番環境のサービスでは導入する価値がありますが、使い捨てのスクリプトでは省略してください。 -
動作と API バージョンを同じコミットで変更しないでください — v2 で可能になる改善も含みます。タイムアウトする代わりに
status == "ERROR"で即座に失敗させるのは、確かな改善であり、次のコミットに含めるべきです。移行のコミットにまとめると、ロールバック時に両方を諦めることになります。こうした変更は別途、クリーンアップの枠に含めてください。
実際のクライアントを使った変更前後の完全な例(GPU フォールバックを伴うポッド作成、コンテナ設定をインライン化したエンドポイント作成、GraphQL ダッシュボード)については、 reference/worked-example.md を参照してください。
5. ライブ API に対して検証する
静的なレビューだけでは不十分です。v2 のバリデーターは厳格で、エラーも正確です。 スキャナーを再実行して呼び出し箇所がなくなったことを確認してから、実際のパスを試します。
python3 "$SCAN" . --scope rest --fail-on-legacy # exit 1 if v1 remains
マーカーは 2 種類あり、それぞれ意味が異なります。間違った方を使わないでください。
| マーカー | 用途 |
|---|---|
rp-migrate: keep-v1 | 意図的に残すレガシーコード — RUNPOD_API_V1 のロールバック分岐、または v2 に相当するものがない GraphQL 呼び出し。意図的に残した項目として報告されます。 |
rp-migrate: ignore | すでに正しいコードに対する誤検出。「これはレガシーではない」という意味であり、「これは残しているレガシーだ」という意味ではありません。誤検出としてマークした項目として報告されます。 |
どちらも line、start/end の範囲、または file スコープを指定できます。また、どちらもプランと --fail-on-legacy の対象から除外されます。誤検出を黙らせるために keep-v1 を使うと、レポートに虚偽の記録が残ります。その場合は ignore を使ってください。
ゲートがまだ誤判定する可能性がある箇所。 ステップ 1 と同じ盲点が、移行後には逆の影響を及ぼします。移行前は v1 のコードを見逃し、移行後は正しい v2 のコードを誤検出することがあります。スキャナーはよくある 2 つのケース、つまり末尾の # was imageName 注釈と、定数に格納されたベース URL(ファイル内のどこかで v2 URL として定義された f"{BASE}/pods")に対応しています。これら以外、たとえば別のモジュールからインポートしたベース URL や、実行時に設定から組み立てた URL については、正しいコードをレガシーと誤認する可能性があります。終了コードを信じる前に該当行を確認し、ゲートを緩めるのではなく、真の誤検出には ignore を付けてください。
- 読み取りは無料です — ポッド、エンドポイント、ボリューム、カタログを一覧表示します。新しいエンベロープ(ベア配列ではなく
{"pods": [...]})を展開していることを確認してください。 - 書き込みには料金がかかります。形状を確認できる最小の対象で、作成 → 検証 → 削除を行います。ユーザーがすでに持っているリソースに対してテストしないでください。
422は reference/breaking-changes.md の表を使って読み解きます。必須フィールドの欠落が原因で、バリデーターが有効なフィールドを「追加プロパティは許可されていません」と報告する、紛らわしいケースも含まれます。
6. 要約する — これが実際に読まれる成果物です
ほとんどのユーザーは差分ではなく要約を読みます。次の形式に厳密に従ってください。
## Required for the migration
<file:line> — what changed and why it had to
## Cleanup enabled by v2
<file:line> — what got deleted or simplified
## Behavior changes to watch
the same-name-different-meaning items that applied
## Still on GraphQL (no v2 equivalent)
myself / spot pods — and why
## Unlocks: what you can build now
tied to what this codebase already does
最後のセクションが最も価値の高い部分です。一般的な機能一覧を貼り付けないでください。このユーザーが何を構築し、何に苦労してきたかを、セッションですでに把握している内容も含めて確認し、v2 によって何が変わるのかを具体的に示してください。「失敗したポッドで wait_until_running ループがタイムアウトします。v2 の ERROR ステータスなら数秒で失敗を検知できます」は、「v2 はより豊富なステータス値を備えています」よりも適切です。reference/unlocks.md には、まさにこのための「コードが X を実行する場合 → v2 では Y が利用可能」という形式で情報がまとめられています。
正しい情報源:自分で仕様を確認する
reference/ の対応表は、2026-08-10 にライブ API と照合済みです。
v2 は活発に開発されているため、これらは手早く確認するための手段として使い、正式な情報源とは見なさないでください。どちらの仕様も公開されており、認証は不要です。
curl -s https://api.runpod.io/v2/openapi.json -o /tmp/rp-v2.json
curl -s https://rest.runpod.io/v1/openapi.json -o /tmp/rp-v1.json
リクエストボディが実際に受け付ける項目と必須項目(*)。作成呼び出しを書く前に実行する価値があります。素朴に生の JSON を読むだけでは見落とす allOf の合成も解決します。
python3 - CreateEndpointRequest <<'PY'
import json, sys
S = json.load(open("/tmp/rp-v2.json"))["components"]["schemas"]
def merge(n, acc=None):
acc = acc if acc is not None else {"props": {}, "req": set()}
if "$ref" in n: return merge(S[n["$ref"].split("/")[-1]], acc)
for sub in n.get("allOf", []): merge(sub, acc)
acc["props"].update(n.get("properties", {})); acc["req"].update(n.get("required", []))
return acc
def kind(v):
if "$ref" in v: return v["$ref"].split("/")[-1]
if "allOf" in v: return kind(v["allOf"][0])
return v.get("type", "?")
m = merge(S[sys.argv[1]])
for k, v in sorted(m["props"].items()):
print(f" {'*' if k in m['req'] else ' '} {k:16} {kind(v)}")
PY
引数を CreatePodRequest、UpdatePodRequest、CreateTemplateRequest、CreateNetworkVolumeRequest、…に置き換えます。どのスキーマにフィールドが記載されているか — 422 に、どこにも指定できない名前が含まれている場合に役立ちます。
python3 -c 'import json,sys; S=json.load(open("/tmp/rp-v2.json"))["components"]["schemas"]; [print(" ",n) for n,s in S.items() if sys.argv[1] in json.dumps(s)]' flashboot
情報源が食い違う場合の優先順位
実際に観測された動作 > 仕様 > これらの表。 仕様が常に正しいとは限りません。また、誤りが判明している箇所については、リファレンスドキュメントにその旨が記載されています。たとえば、timeout のデフォルト値は 300000 ms と文書化されていますが、実際には 0 が返されます。実行中の API が仕様と矛盾するケースに遭遇したら、API の動作を信頼し、概要でその旨を伝えてください。文書化されたデフォルト値があてにならないことを、ユーザーが把握できるようにします。
reference/ にあるマッピングが仕様と一致しなくなっていることに気づいた場合は、呼び出しを修正し、乖離を明記してください。表には検証日が記載されているため、古くなった情報を見逃さずに検出できます。
GraphQL には機械可読なスキーマがありません(イントロスペクションが無効になっています)。そのため、この方法では GraphQL 側を検証できません。詳しくは reference/graphql-to-v2.md の注意事項を参照してください。
ツールに関する注意事項
インベントリスキャナーは、意図的にコードグラフのインデックスではなく、grep クラスのスクリプトとして設計されています。API の世代は URL 文字列とフィールド名で判定でき、任意の顧客リポジトリでどの言語にも対応し、セットアップ不要で、すべてのユーザーに同じ結果を返す必要があるためです。コードインテリジェンスのインデックス(LSP、またはすでに実行中の MCP グラフサーバー)が役立つのは、別のステップ、つまりステップ 1 の影響範囲を調べる場面です。「レスポンスフィールドの名前が変更されたこのラッパーを呼び出しているのは誰か」という問いへの回答には役立ちますが、検出には必要ありません。すでに利用できる場合はその場面で使用してください。この目的だけのために新たに用意する必要はありません。