Skip to content

fix(backup): survive iCloud's fresh-install metadata race; resumable restore (build 11) - #21

Merged
markm39 merged 1 commit into
mainfrom
fix/icloud-restore-download
Aug 30, 2026
Merged

fix(backup): survive iCloud's fresh-install metadata race; resumable restore (build 11)#21
markm39 merged 1 commit into
mainfrom
fix/icloud-restore-download

Conversation

@markm39

@markm39 markm39 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

The on-device failure (1.3 build 10)

Real-device TestFlight test: restore brought back note titles but no content. Root cause: a fresh install's ubiquity container is cloud-only until iCloud syncs metadata down — plain FileManager walks see nothing, the old ensureDownloaded gave up when neither file nor placeholder existed, and installing the catalog on partial restore permanently blocked the retry prompt. The dev-override simulator E2E structurally cannot reproduce this; only a device can.

Fix (three layers)

  1. See the cloud: listCloudFiles via NSMetadataQuery (the canonical discovery API — sees un-materialized items and nudges sync), merged with the FS walk; ensureDownloaded retries startDownloadingUbiquitousItem until deadline. Review-hardened: /private symlink standardization so prefix filtering can't silently no-op, cloud-only dirs classified by content type, verified query lifecycle (no hang/leak/double-resolve).
  2. Meta files race too: manifest + backup catalog reads are download-forced (the mirror-image race — bodies first, catalog cloud-only — would have permanently stubbed titles).
  3. Resumable, self-healing restore: catalog restore is now a merge (backup wins only for missing notes and recovery stubs; local notes/tombstones untouched), and a silent per-launch resume completes any interrupted restore — missing bodies pulled, stub titles healed — gated to files iCloud still lists so stale manifest entries can't loop forever. Devices already damaged by build 10 self-heal on first launch of build 11.

Verification

  • 48 node tests: device repro (partial → resume → complete), mirror-image race (stub-title heal), stale-manifest no-loop, merge semantics, plus the full prior suite
  • Simulator E2E of the exact broken device state (titles present incl. one stub, zero bodies): fully healed silently on launch
  • Rigorous review pass applied (HIGH: meta download-forcing; MEDIUMs: symlink, dir classification, retry-loop bound)
  • Typecheck, Metro export, native sim build clean; bumped to build 11

Real-iCloud validation happens on TestFlight build 11: enable → note → delete app → reinstall → restore must bring back content.

https://claude.ai/code/session_01Vy3wDR2rgpvNKrbBpCbrEY

…re resumable (build 11)

On a real device a fresh install's ubiquity container is cloud-only
until metadata syncs; the restore used plain filesystem walks, missed
the un-materialized note bodies, and installing the catalog blocked any
retry - notes came back with titles but no content (found on-device in
1.3 build 10; the dev-override simulator E2E structurally cannot catch
this).

- ICloudBackupModule: listCloudFiles via NSMetadataQuery (sees cloud-
  only items and nudges the sync; main-queue lifecycle with timeout and
  double-completion guard; /private symlink standardization; cloud-only
  directories classified by content type). ensureDownloaded now retries
  startDownloadingUbiquitousItem until the deadline instead of giving
  up when metadata has not arrived.
- backupEngine: manifest and backup catalog reads are download-forced
  too (the same race can hit them). Restore now MERGES the backup
  catalog - backup metadata adopted for missing notes and recovery
  stubs, local notes and tombstones untouched - and is resumable:
  resumeRestoreIfIncomplete heals notes-without-bodies and stub titles
  on every launch, gated to files iCloud still lists so a stale
  manifest entry cannot cause a retry-forever loop.
- useBackup runs the silent resume before the startup sync, so devices
  already damaged by build 10 self-heal on first launch of this build.

Verified: 48 node tests including the device repro (partial restore
resumes to completion) and the mirror-image race (stub titles heal);
simulator E2E of the exact broken device state - catalog with titles,
zero bodies, one a stub - fully healed on launch with no prompts.

Claude-Session: https://claude.ai/code/session_01Vy3wDR2rgpvNKrbBpCbrEY
@markm39
markm39 merged commit f304ef8 into main Aug 30, 2026
@markm39
markm39 deleted the fix/icloud-restore-download branch August 30, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant