fix(backup): survive iCloud's fresh-install metadata race; resumable restore (build 11) - #21
Merged
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ensureDownloadedgave 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)
listCloudFilesviaNSMetadataQuery(the canonical discovery API — sees un-materialized items and nudges sync), merged with the FS walk;ensureDownloadedretriesstartDownloadingUbiquitousItemuntil 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).Verification
Real-iCloud validation happens on TestFlight build 11: enable → note → delete app → reinstall → restore must bring back content.
https://claude.ai/code/session_01Vy3wDR2rgpvNKrbBpCbrEY