AIコーディングで同じ失敗を繰り返さないために:Google Drive API と Xcode Cloud の教訓
はじめに
前回の記事では、Cursor の AI エージェントと iOS アプリのクラッシュ修正から CI 構築までを1日で進めた話を書いた。あれから数日、さらに開発を進める中で、AI コーディングの根本的な課題にぶつかった。
AIが自信満々に間違える。しかも、同じカテゴリのミスを繰り返す。
この記事では、Google Drive API と Xcode Cloud で起きた2つの事例を通して、なぜ AI は外部サービスの仕様で間違えやすいのか、そしてどうすれば「ドキュメントを先に学ばせる」仕組みを作れるのかを書く。
事例1:Google Drive API の 403 Forbidden
5回の誤診
TestFlight のベータテスターから「アクセスが拒否されました」というフィードバックが届いた。サインインし直しても直らない。
AI に調査を依頼したところ、こんな推論が返ってきた。
- 「OAuth のスコープが Google Cloud Console に登録されていないのでは?」 — 確認したが問題なし
- 「OAuth 同意画面がテストモードのままでは?」 — 本番モードだった
- 「テストユーザーのリストに追加されていないのでは?」 — 本番モードにはテストユーザーの概念がない
どれも外れだった。AI はもっともらしい原因を次々と提案するが、実際の API レスポンスを見ていない。
エラーの中身を見る
転機になったのは、API レスポンスのボディをログに出すことだった。それまで HTTP ステータスコード(403)だけで判断していたが、レスポンスボディにはこう書かれていた。
The parents field is not directly writable in update requests.
Use the addParents and removeParents parameters instead.
原因は明白だった。ファイル更新の PATCH リクエストに parents フィールドを含めていた。Google Drive API v3 では、これは禁止されている。
なぜ AI は間違えたのか
Google Drive API の公式ドキュメントには明記されている。
- POST(ファイル作成): メタデータに
parentsを含める → OK - PATCH(ファイル更新): メタデータに
parentsを含める → 403 Forbidden
これは API ドキュメントの「制限事項」セクションに書かれている類の情報であり、LLM のトレーニングデータに正確に反映されているとは限らない。AI は POST と PATCH で共通のメタデータ構造を使う方が「自然」だと判断し、parents を両方に含めるコードを書いた。
修正は1行
// PATCH(更新)時は parents を含めない(Drive API v3 の制約)
if existingFileId == nil {
metadata["parents"] = [folderId]
}
修正自体は些末だ。問題は、この仕様に到達するまでに5回の誤診と数時間を費やしたことだ。
教訓: AI が外部 API のエラーを推測で診断し始めたら、まずエラーレスポンスのボディを確認する。ステータスコードだけでは原因は特定できない。
事例2:Xcode Cloud のビルド番号
「ビルド番号が重複しています」の無限ループ
TestFlight への自動配信のために Xcode Cloud を設定した。最初のビルドは成功したが、2回目以降で毎回こうなる。
The bundle version must be higher than the previously uploaded version.
ビルド番号がインクリメントされていない。AI に修正を依頼したところ、こんな試行錯誤が始まった。
ci_post_clone.sh で agvtool new-version -all $((CI_BUILD_NUMBER + 100)) を実行 → プロジェクトに VERSIONING_SYSTEM = "apple-generic" がなく、agvtool が動作せず
sed と PlistBuddy で project.pbxproj と Info.plist を書き換え → Xcode Cloud の内部カウンターに上書きされた
なぜ3回も間違えたのか
AI は「ビルド番号の管理」という一般的なタスクに対して、従来の CI/CD(GitHub Actions, Fastlane 等)で使われるパターンを適用した。これらの環境ではスクリプトでビルド番号を制御するのが標準的だ。
しかし Xcode Cloud は違う。Apple のドキュメントには、ビルド番号は Xcode Cloud が内部で管理し、スクリプトでの上書きは意図した動作にならない旨が書かれている。
これは「知らなければ絶対にたどり着けない」タイプの仕様だ。AI は一般的なパターンから類推するため、プラットフォーム固有の例外に弱い。
教訓: AI が「これで動くはず」と言ったのに失敗したら、まず公式ドキュメントの「制限事項」を確認する。プラットフォーム固有の仕様は、一般的なパターンの類推では到達できない。
パターンの共通点
2つの事例に共通するのは、以下のパターンだ。
- AI は一般的な知識から自信を持って推論する(「REST API は POST も PATCH も同じメタデータだろう」「CI のビルド番号はスクリプトで制御するものだろう」)
- プラットフォーム固有の制約を知らない(Drive API v3 の PATCH 制限、Xcode Cloud の内部カウンター)
- 失敗しても同じカテゴリの推測を続ける(OAuth 設定→Cloud Console→テストユーザー... すべて「認証周り」の推測)
これは AI コーディングツール全般に当てはまる構造的な問題だ。LLM は確率的に「ありそうな答え」を生成するが、特定の API バージョンの特定の制約までは保証できない。
対策:ドキュメントを先に学ばせる
根本対策は、AI がコードを書く前に、正しい仕様を参照できる状態を作ることだ。
Cursor には .cursor/rules/ というプロジェクト固有のルールファイルを置く仕組みがある(前回の記事参照)。ここに外部サービスの仕様書を置くことで、AI が毎セッション開始時に読み込むようにできる。
1. 外部サービスの仕様ファイル
今回の教訓を .cursor/rules/external-services.mdc としてまとめた。
# 外部サービスの仕様・注意事項
AI がこのプロジェクトで外部 API や CI/CD を扱う際に
必ず参照すること。
ここに記載のない外部サービスの仕様については、
公式ドキュメントを @web で確認してから実装する。
## Google Drive API v3
### ファイル更新(PATCH)で parents を含めてはいけない
- POST(新規作成): メタデータに parents を含める → OK
- PATCH(更新): メタデータに parents を含める → 403 Forbidden
- 参考: https://developers.google.com/.../files/update
### エラーレスポンスは必ずボディを確認する
- HTTP ステータスコードだけでは原因を特定できない
- レスポンスボディの JSON に具体的なエラー理由が含まれる
## Xcode Cloud
### ビルド番号は Xcode Cloud が自動管理する
- CURRENT_PROJECT_VERSION を agvtool や sed で
上書きしても無視される
- App Store Connect → Xcode Cloud → 設定 →
ビルド番号で「次のビルド番号」を設定する
ポイントは3つ。
- 「何をすべきか」ではなく「何をしてはいけないか」を書く — AI は「やれること」は自力で推論できる。間違えやすい制約こそ明示する
- 公式ドキュメントの URL を添える — AI が詳細を確認できるようにする
alwaysApply: trueにする — 明示的に参照しなくても、毎セッション自動で読み込まれる
2. 「調査ファースト」ルール
既存の基本ルールファイルに、以下のルールを追加した。
## 調査ファースト
- 外部 API・サービスを扱う変更の前に、
必ず .cursor/rules/external-services.mdc を確認する
- 該当ルールに記載がない場合は @web で
公式ドキュメントを検索してから実装する
- 「たぶんこうだろう」で推測実装しない。
ドキュメントの裏付けを取る
- 新しく判明した制約やハマりポイントは
external-services.mdc に追記する
最後の項目がキモだ。新しいハマりポイントを発見したら、ルールファイルに追記する。つまり、失敗のたびにルールが成長し、同じ失敗を二度と繰り返さなくなる。
3. アンチパターンの蓄積
コーディング規約ファイル(.cursor/rules/swift-conventions.mdc)にも、開発中に発見した具体的なアンチパターンを追記するセクションを設けた。
## アンチパターン(Lessons Learned)
- HTTP エラーを一括で「ネットワークエラー」にしない
→ ステータスコード別に分類する(401/403/404/429/5xx)
- URLSession のレスポンスボディを捨てない
→ エラー時も data を読んでログに記録する
- Dictionary(uniqueKeysWithValues:) は
データに重複がないことが保証されている場合のみ
→ 外部データには Dictionary(_:uniquingKeysWith:) を使う
<!-- 開発中に AI が繰り返すミスがあれば、ここに追記 -->
この仕組みの設計思想
いくつかの設計判断について補足する。
なぜ Cursor Rules なのか
代替手段として、以下も考えられる。
| 方法 | メリット | デメリット |
|---|---|---|
| Cursor Rules ( .cursor/rules/) |
セッション開始時に自動読み込み。リポジトリにコミットでき、チーム共有可能 | Cursor 固有の仕組み |
| プロンプトで毎回指示 | ツール非依存 | 毎回手動で貼る必要がある。忘れる |
| README やドキュメント | ツール非依存 | AI が自動で読む保証がない |
| コード内コメント | コードと一緒に読まれる | 該当ファイルを開かないと読まれない。プロジェクト横断の制約には不向き |
Cursor Rules を選んだ理由は「忘れない」ことだ。alwaysApply: true にしておけば、どのファイルを開いていても自動で読み込まれる。人間がプロンプトを貼り忘れるリスクがゼロになる。
ツールロックインの問題
Cursor Rules は Cursor 固有の仕組みだが、中身はただの Markdown テキストだ。Claude Code の CLAUDE.md や GitHub Copilot の instructions ファイルなど、他のツールに移行する際もテキストの流用は容易だ。
重要なのはファイル形式ではなく、「外部サービスの制約を機械可読な形で蓄積する」という習慣そのものだ。
メンテナンスコスト
「ルールファイルの更新が面倒では?」という懸念があるかもしれない。実際には、ハマった直後が最もモチベーションが高いため、その場で追記するのは自然だ。むしろ、数時間のデバッグの末にようやく解決した制約を「どこにも書かない」方が不自然だろう。
さらに、AI 自身にルールの追記を指示することもできる。「今わかったことを external-services.mdc に追記して」と言えば、AI がフォーマットに合わせて追記する。AI の学習を AI 自身に任せるわけだ。
全体像:プロジェクトのルール構成
現時点で .cursor/rules/ に置いているファイルは5つ。
| ファイル | 用途 | 適用条件 |
|---|---|---|
general.mdc | 言語、思考プロセス、調査ファースト | 常時 |
project-context.mdc | アーキテクチャ、データ同期の設計 | 常時 |
swift-conventions.mdc | コーディング規約、アンチパターン | *.swift |
github-workflow.mdc | Issue/PR ワークフロー | 常時 |
external-services.mdc | 外部 API の仕様と制約 | 常時 |
この構成は「ゼロから AI に教える」のではなく、「AI が間違えた箇所だけを補正する」アプローチだ。AI は Swift の書き方や SwiftUI の使い方は(概ね)知っている。知らないのは、このプロジェクト固有のコンテキストと、特定 API の罠だ。そこだけを重点的にカバーする。
まとめ
AI コーディングツールは「コードを書く」能力は高いが、「外部サービスの仕様を正確に知っている」とは限らない。特に以下の領域では注意が必要だ。
- REST API の「許可されていないフィールド」や「メソッド間の非対称な仕様」
- CI/CD プラットフォームの「内部で自動管理される設定」
- プラットフォーム固有の制約(Apple/Google の独自ルール)
対策として有効だったのは、次の3点。
- エラーレスポンスのボディを必ず読む — AI の推測に頼る前に事実を確認する
- ハマりポイントをルールファイルに蓄積する — 同じ失敗を二度繰り返さない仕組み
- 「調査ファースト」をルール化する — 外部サービスの変更は推測で実装しないよう制約する
AI の知識は確率的だが、ルールファイルは確定的だ。「AI が知らないこと」を明示的に教えておけば、推測の精度は飛躍的に上がる。
失敗は避けられない。だが、同じ失敗を繰り返すのは避けられる。