mcpField
A
axiom-cloud-sync-diag

axiom-cloud-sync-diag

github.com/charleswiltgenクラウド・DevOps1.2k180 回インストール今日更新
リポジトリ

axiom-cloud-sync-diag について

iCloud 同期を調査する Apple プラットフォーム開発者向けの診断スキルです。アカウント、接続、権限の確認手順と、CloudKit および iCloud Drive の一般的なエラーを扱います。

主な機能

  1. 01アカウントと権限の確認
  2. 02CloudKit エラーの診断
  3. 03iCloud Drive 転送の診断
  4. 04同期競合の分析
  5. 05クライアントログと監視の指針

活用シーン

  1. 01デバイス間でデータが届かない原因の調査
  2. 02CloudKit の容量やネットワークエラーの診断
  3. 03ファイルのアップロード・ダウンロード停滞の調査
  4. 04アプリ更新後の同期障害の追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-cloud-sync-diag

インストール後、Claude Code で/axiom-cloud-sync-diagを入力して読み込まれたか確認します。

詳細

iCloud 同期の診断

概要

基本原則 クラウド同期の問題の90%は、アカウントや利用資格の問題、ネットワーク接続、または同期タイミングの誤解が原因であり、iCloud インフラストラクチャのバグではありません。

iCloud(CloudKit と iCloud Drive の両方)は、すべての Apple デバイスで毎日数十億件の同期処理を処理しています。データが同期されない場合、ほとんどの場合、原因は設定、接続、またはタイミングに対する認識です。

危険信号 — クラウド同期の問題を疑う

次のいずれかが見られる場合:

  • ファイルやデータがほかのデバイスに表示されない
  • 「iCloud アカウントを利用できません」というエラー
  • 同期の競合が解消されない
  • CloudKit のクォータ超過
  • アップロード/ダウンロードが0%で停止する
  • シミュレーターでは動作するが、デバイスでは動作しない
  • WiFi では動作するが、モバイル通信では動作しない

❌ 禁止 「iCloud が壊れているので、独自の同期機能を構築すべき」

  • iCloud インフラストラクチャは数兆件の処理を処理しています
  • 信頼性の高い同期機能の構築は非常に複雑です
  • 問題の99%は設定または接続が原因です

必須の最初の手順

コードを変更する前に、必ず最初に以下を確認してください:

// 1. Check iCloud account status
func checkICloudStatus() async {
    let status = FileManager.default.ubiquityIdentityToken

    if status == nil {
        print("❌ Not signed into iCloud")
        print("Settings → [Name] → iCloud → Sign in")
        return
    }

    print("✅ Signed into iCloud")

    // For CloudKit specifically
    let container = CKContainer.default()
    do {
        let status = try await container.accountStatus()
        switch status {
        case .available:
            print("✅ CloudKit available")
        case .noAccount:
            print("❌ No iCloud account")
        case .restricted:
            print("❌ iCloud restricted (parental controls?)")
        case .couldNotDetermine:
            print("⚠️ Could not determine status")
        case .temporarilyUnavailable:
            print("⚠️ Temporarily unavailable (retry)")
        @unknown default:
            print("⚠️ Unknown status")
        }
    } catch {
        print("Error checking CloudKit: \(error)")
    }
}

// 2. Check entitlements
func checkEntitlements() {
    // Verify iCloud container exists
    if let containerURL = FileManager.default.url(
        forUbiquityContainerIdentifier: nil
    ) {
        print("✅ iCloud container: \(containerURL)")
    } else {
        print("❌ No iCloud container")
        print("Check Xcode → Signing & Capabilities → iCloud")
    }
}

// 3. Check network connectivity
func checkConnectivity() {
    // Use NWPathMonitor or similar
    print("Network: Check if device has internet")
    print("Try on different networks (WiFi, cellular)")
}

// 4. Check device storage
func checkStorage() {
    let homeURL = FileManager.default.homeDirectoryForCurrentUser
    if let values = try? homeURL.resourceValues(forKeys: [
        .volumeAvailableCapacityKey
    ]) {
        let available = values.volumeAvailableCapacity ?? 0
        print("Available space: \(available / 1_000_000) MB")

        if available < 100_000_000 {  // <100 MB
            print("⚠️ Low storage may prevent sync")
        }
    }
}

判断ツリー

CloudKit の同期問題
CloudKit data not syncing?

├─ Account unavailable?
│   ├─ Check: await container.accountStatus()
│   ├─ .noAccount → User not signed into iCloud
│   ├─ .restricted → Parental controls or corporate restrictions
│   └─ .temporarilyUnavailable → Network issue or iCloud outage
│
├─ CKError.quotaExceeded?
│   └─ User exceeded iCloud storage quota
│       → Prompt user to purchase more storage
│       → Or delete old data
│
├─ CKError.networkUnavailable?
│   └─ No internet connection
│       → Check WiFi/cellular
│       → Test on different network
│
├─ CKError.serverRecordChanged (conflict)?
│   └─ Concurrent modifications
│       → Implement conflict resolution
│       → Use savePolicy correctly
│
└─ SwiftData not syncing?
    ├─ Check ModelConfiguration CloudKit setup
    ├─ Verify private database only (no public/shared)
    └─ Check for @Attribute(.unique) (not supported with CloudKit)
iCloud Drive の同期問題
iCloud Drive files not syncing?

├─ File not uploading?
│   ├─ Check: url.resourceValues(.ubiquitousItemIsUploadingKey)
│   ├─ Check: url.resourceValues(.ubiquitousItemUploadingErrorKey)
│   └─ Error details will indicate issue
│
├─ File not downloading?
│   ├─ Not requested? → startDownloadingUbiquitousItem(at:)
│   ├─ Check: url.resourceValues(.ubiquitousItemDownloadingErrorKey)
│   └─ May need manual download trigger
│
├─ File has conflicts?
│   ├─ Check: url.resourceValues(.ubiquitousItemHasUnresolvedConflictsKey)
│   └─ Resolve with NSFileVersion
│
└─ Files not appearing on other device?
    ├─ Check iCloud account on both devices (same account?)
    ├─ Check entitlements match on both
    ├─ Wait (sync not instant, can take minutes)
    └─ Check Settings → iCloud → iCloud Drive → [App] is enabled

よくある CloudKit エラー

CKError.accountTemporarilyUnavailable

原因:iCloud サーバーが一時的に利用できないか、ユーザーがサインアウトしています

修正方法:

if error.code == .accountTemporarilyUnavailable {
    // Retry with exponential backoff
    try await Task.sleep(for: .seconds(5))
    try await retryOperation()
}
CKError.quotaExceeded

原因:ユーザーの iCloud ストレージがいっぱいです

修正方法:

if error.code == .quotaExceeded {
    // Show alert to user
    showAlert(
        title: "iCloud Storage Full",
        message: "Please free up space in Settings → [Name] → iCloud → Manage Storage"
    )
}
CKError.serverRecordChanged

原因:最後に取得してからサーバー上のレコードが変更されています。最も一般的な根本原因:最新バージョンを先に取得せず、古いレコードを保存しています。

診断 — まず簡単な修正方法を確認してください:

// ❌ WRONG: Saving without fetching latest version
// This causes serverRecordChanged on EVERY concurrent edit
let record = CKRecord(recordType: "Note", recordID: existingID)
record["title"] = "Updated"
try await database.save(record)  // Overwrites server version → conflict

// ✅ FIX: Fetch-then-modify-then-save (fixes 80% of cases)
let record = try await database.record(for: existingID)  // Get latest
record["title"] = "Updated"  // Modify the fetched record
try await database.save(record)  // Save with correct changeTag

取得してから保存しても解決しない場合(複数デバイスからの実際の同時編集):

if error.code == .serverRecordChanged,
   let serverRecord = error.serverRecord,
   let clientRecord = error.clientRecord {
    // Merge records — only needed for real multi-device conflicts
    let merged = mergeRecords(server: serverRecord, client: clientRecord)
    try await database.save(merged)
}
CKError.networkUnavailable

原因:インターネットに接続されていません

修正方法:

if error.code == .networkUnavailable {
    // Queue for retry when online
    queueOperation(for: .whenOnline)

    // Or show offline indicator
    showOfflineIndicator()
}
バッチ処理でのサイレントなデータ損失

症状:同期は動作しているように見えますが、レコードが通知なく消失したり、保存に失敗したりします。

よくある原因:

原因症状修正方法
レコードサイズが1 MB超個々のレコードがバッチから通知なく除外される大きなデータを CKAsset に分割する
バッチの一部失敗一部のレコードは保存される一方、ほかは通知なく失敗するレコードごとのエラーを確認するため、perRecordSaveBlock を確認する
競合の自動解決最後に書き込んだ値が優先され、有効なデータが上書きされるマージベースの競合解決を実装する
アセットのダウンロードが開始されないレコードは同期されるが、CKAsset の内容がないdesiredKeys を指定して fetchRecordZoneChanges を呼び出す

診断:

// ❌ WRONG: Batch save with no per-record error handling
let operation = CKModifyRecordsOperation(recordsToSave: records)
operation.modifyRecordsResultBlock = { result in
    // Only catches operation-level failures — misses per-record errors
}

// ✅ CORRECT: Check each record individually
let operation = CKModifyRecordsOperation(recordsToSave: records)
operation.perRecordSaveBlock = { recordID, result in
    switch result {
    case .success(let record):
        print("✅ Saved: \(recordID)")
    case .failure(let error):
        print("❌ Failed: \(recordID) — \(error)")
        // Log for retry — this record was silently lost otherwise
    }
}

よくある iCloud Drive エラー

アップロードエラー
// ✅ Check upload error
func checkUploadError(url: URL) {
    let values = try? url.resourceValues(forKeys: [
        .ubiquitousItemUploadingErrorKey
    ])

    if let error = values?.ubiquitousItemUploadingError {
        print("Upload error: \(error.localizedDescription)")

        if (error as NSError).code == NSFileWriteOutOfSpaceError {
            print("iCloud storage full")
        }
    }
}
ダウンロードエラー
// ✅ Check download error
func checkDownloadError(url: URL) {
    let values = try? url.resourceValues(forKeys: [
        .ubiquitousItemDownloadingErrorKey
    ])

    if let error = values?.ubiquitousItemDownloadingError {
        print("Download error: \(error.localizedDescription)")

        // Common errors:
        // - Network unavailable
        // - Account unavailable
        // - File deleted on server
    }
}

デバッグパターン

パターン1:CloudKit の処理が完了しない

症状:保存/取得が完了せず、エラーも発生しません

診断:

// Add timeout
Task {
    try await withTimeout(seconds: 30) {
        try await database.save(record)
    }
}

// Log operation lifecycle
operation.database = database
operation.completionBlock = {
    print("Operation completed")
}
operation.qualityOfService = .userInitiated

// Check if operation was cancelled
if operation.isCancelled {
    print("Operation was cancelled")
}

よくある原因:

  • ネットワークに接続されていない
  • アカウントの問題
  • 処理が早期にキャンセルされた
パターン2:SwiftData CloudKit が同期されない

症状:SwiftData はローカルに保存されますが、同期されません

診断:

// 1. Verify CloudKit configuration
let config = ModelConfiguration(
    cloudKitDatabase: .private("iCloud.com.example.app")
)

// 2. Check for incompatible attributes
// ❌ @Attribute(.unique) not supported with CloudKit
@Model
class Task {
    @Attribute(.unique) var id: UUID  // ← Remove this
    var title: String
}

// 3. Check all properties have defaults or are optional
@Model
class Task {
    var title: String = ""  // ✅ Has default
    var dueDate: Date?      // ✅ Optional
}
パターン3:ファイルコーディネーターのデッドロック

症状:ファイル操作がハングします

診断:

// ❌ WRONG: Nested coordination can deadlock
coordinator.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
    // Don't create another coordinator here!
    anotherCoordinator.coordinate(...)  // ← Deadlock risk
}

// ✅ CORRECT: Single coordinator per operation
coordinator.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
    // Direct file operations only
    try data.write(to: newURL)
}
パターン4:競合が解決されない

症状:解決した後も競合が続きます

診断:

// ❌ WRONG: Not marking as resolved
let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
for conflict in conflicts ?? [] {
    // Missing: conflict.isResolved = true
}

// ✅ CORRECT: Mark resolved and remove
for conflict in conflicts ?? [] {
    conflict.isResolved = true
}
try NSFileVersion.removeOtherVersionsOfItem(at: url)

本番環境の障害シナリオ

症状:アプリのアップデート後、データが同期されないという報告がユーザーから寄せられる

診断手順(順番に実行):

  1. アカウントの状態を確認(2 分):

    // On affected device
    let status = FileManager.default.ubiquityIdentityToken
    // nil? → Not signed in
    
  2. エンタイトルメントが変更されていないことを確認(5 分):

    • 旧ビルドと新ビルドのエンタイトルメントを比較します
    • コンテナ ID が一致していることを確認します
  3. 互換性を損なう変更がないか確認(10 分):

    • CloudKit スキーマに変更がありましたか?
    • ユビキタスコンテナ ID に変更がありましたか?
    • 旧バージョンと新バージョンに互換性がありますか?
  4. クリーンなデバイスでテスト(15 分):

    • デバイスを工場出荷時の状態にリセットするか、新しいテストデバイスを使用します
    • iCloud にサインインします
    • アプリをインストールします
    • 新規インストール時に同期は機能しますか?

根本原因(ケースの 90%):

  • ビルド内のエンタイトルメントが変更または破損している
  • CloudKit コンテナ ID が一致していない
  • スキーマに互換性を損なう変更がある
  • アカウントの制限(新しいペアレンタルコントロールなど)

修正方法:

  • ビルド内のエンタイトルメントを確認します
  • 旧バージョンからの移行経路をテストします
  • エラー処理とユーザー向けメッセージを改善します

モニタリング

CloudKit Console(推奨 - WWDC 2024)

アクセス先:https://icloud.developer.apple.com/dashboard

モニタリング項目:

  • 種類別のエラー率
  • レイテンシのパーセンタイル(p50、p95、p99)
  • クォータ使用量
  • リクエスト数

次の場合にアラートを設定:

  • エラー率が高い(>5%)
  • クォータが上限に近づいている(>80%)
  • レイテンシの急増
クライアント側のロギング
// ✅ Log all CloudKit operations
extension CKDatabase {
    func saveWithLogging(_ record: CKRecord) async throws {
        print("Saving record: \(record.recordID)")
        let start = Date()

        do {
            try await self.save(record)
            let duration = Date().timeIntervalSince(start)
            print("✅ Saved in \(duration)s")
        } catch let error as CKError {
            print("❌ Save failed: \(error.code), \(error.localizedDescription)")
            throw error
        }
    }
}

簡易診断チェックリスト

func diagnoseCloudSyncIssue() async {
    print("=== Cloud Sync Diagnosis ===")

    // 1. Account
    await checkICloudStatus()

    // 2. Entitlements
    checkEntitlements()

    // 3. Network
    checkConnectivity()

    // 4. Storage
    checkStorage()

    // 5. For CloudKit
    let container = CKContainer.default()
    do {
        let status = try await container.accountStatus()
        print("CloudKit status: \(status)")
    } catch {
        print("CloudKit error: \(error)")
    }

    // 6. For iCloud Drive
    if let url = getICloudContainerURL() {
        let values = try? url.resourceValues(forKeys: [
            .ubiquitousItemDownloadingErrorKey,
            .ubiquitousItemUploadingErrorKey
        ])
        print("Download error: \(values?.ubiquitousItemDownloadingError?.localizedDescription ?? "none")")
        print("Upload error: \(values?.ubiquitousItemUploadingError?.localizedDescription ?? "none")")
    }

    print("=== End Diagnosis ===")
}

関連スキル

  • axiom-cloudkit-ref — CloudKit の実装詳細
  • axiom-icloud-drive-ref — iCloud Drive の実装詳細
  • axiom-storage — 同期方式の選択

最終更新日:2025-12-12 スキルの種類:診断

コメント