Skip to content

๐Ÿ› [fix] iCloud ๋™๊ธฐํ™” ์˜๊ตฌ ์ค‘๋‹จ(UC ๋ถ€์žฌ ๋ธŒ๋ฆญ) ์ƒํƒœ self-heal ๋ณต๊ตฌ ์ถ”๊ฐ€#182

Open
geeksbaek wants to merge 2 commits into
developfrom
claude/icloud-sync-broken-awvbx8
Open

Conversation

@geeksbaek

@geeksbaek geeksbaek commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

hasEverHadUserCollection=true์ธ๋ฐ UC๊ฐ€ 0๊ฐœ์ธ ์ƒํƒœ์—์„œ CloudKit re-import๊ฐ€
UC๋ฅผ ๋˜๋Œ๋ ค์ฃผ์ง€ ๋ชปํ•˜๋ฉด(ํด๋ผ์šฐ๋“œ์— UC ์—†์Œ, iCloud ๋กœ๊ทธ์•„์›ƒ/์ œํ•œ ๋“ฑ)
getUserCollection()์ด ์˜๊ตฌํžˆ .notFound๋ฅผ throwํ•˜์—ฌ ๋ชจ๋“  ์ €์žฅ/๋™๊ธฐํ™”๊ฐ€
์กฐ์šฉํžˆ ์‹คํŒจํ•˜๊ณ  ์„ค์ • ํ™”๋ฉด์ด "iCloud ๋ฐ์ดํ„ฐ ๋Œ€๊ธฐ ์ค‘..."์— ๊ณ ์ •๋˜๋Š” ๋ฌธ์ œ ์ˆ˜์ •.

  • re-import ๊ฐ€๋Šฅ์„ฑ์ด ์—†๋‹ค๊ณ  ํŒ๋‹จ๋˜๋ฉด ์ƒˆ UC ์ƒ์„ฑ ํ—ˆ์šฉ (self-heal):
    • import_settled: ์„ธ์…˜ ๋‚ด Import ์„ฑ๊ณต + grace period ๊ฒฝ๊ณผ ํ›„์—๋„ UC ์—†์Œ
    • no_icloud_account: ๊ณ„์ • .noAccount/.restricted โ€” import๊ฐ€ ์˜ฌ ์ˆ˜ ์—†์Œ
    • persisted_24h: UC ๋ถ€์žฌ 24์‹œ๊ฐ„ ์ง€์† ๋ฐฑ์Šคํ†ฑ (UserDefaults ํƒ€์ž„์Šคํƒฌํ”„)
  • checkiCloudAccountStatus()์—์„œ lastKnownAccountStatus ์บ์‹ฑ
  • Import ์ข…๋ฃŒ ํ›„ UC๊ฐ€ ์—†์œผ๋ฉด grace ๋งŒ๋ฃŒ ์งํ›„ Path-B ๊ฐฑ์‹  ์žฌํŠธ๋ฆฌ๊ฑฐ
    (scheduleSelfHealRecheckIfNeeded) โ€” ์‚ฌ์šฉ์ž ์กฐ์ž‘ ์—†์ด๋„ UI ๋ณต๊ตฌ
  • UC ์ •์ƒ ์กฐํšŒ/์˜๋„์  ์ดˆ๊ธฐํ™” ์‹œ ๋ถ€์žฌ ๊ด€์ธก ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ฆฌ
  • Analytics: sync_uc_created path=self_heal + reason ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01PDsJ57bUdibeuKL5P72y6f

Summary by CodeRabbit

  • Bug Fixes
    • Improved recovery when a user collection is missing after iCloud sync, reducing repeated โ€œnot foundโ€ failures and stuck states.
    • Added automatic self-healing behavior that can recreate missing collection data after sync settles, when iCloud is unavailable, or after a 24-hour fallback window.
    • Added a delayed post-import recheck so the app can refresh once missing data is eventually restored.

claude added 2 commits July 6, 2026 16:36
hasEverHadUserCollection=true์ธ๋ฐ UC๊ฐ€ 0๊ฐœ์ธ ์ƒํƒœ์—์„œ CloudKit re-import๊ฐ€
UC๋ฅผ ๋˜๋Œ๋ ค์ฃผ์ง€ ๋ชปํ•˜๋ฉด(ํด๋ผ์šฐ๋“œ์— UC ์—†์Œ, iCloud ๋กœ๊ทธ์•„์›ƒ/์ œํ•œ ๋“ฑ)
getUserCollection()์ด ์˜๊ตฌํžˆ .notFound๋ฅผ throwํ•˜์—ฌ ๋ชจ๋“  ์ €์žฅ/๋™๊ธฐํ™”๊ฐ€
์กฐ์šฉํžˆ ์‹คํŒจํ•˜๊ณ  ์„ค์ • ํ™”๋ฉด์ด "iCloud ๋ฐ์ดํ„ฐ ๋Œ€๊ธฐ ์ค‘..."์— ๊ณ ์ •๋˜๋Š” ๋ฌธ์ œ ์ˆ˜์ •.

- re-import ๊ฐ€๋Šฅ์„ฑ์ด ์—†๋‹ค๊ณ  ํŒ๋‹จ๋˜๋ฉด ์ƒˆ UC ์ƒ์„ฑ ํ—ˆ์šฉ (self-heal):
  - import_settled: ์„ธ์…˜ ๋‚ด Import ์„ฑ๊ณต + grace period ๊ฒฝ๊ณผ ํ›„์—๋„ UC ์—†์Œ
  - no_icloud_account: ๊ณ„์ • .noAccount/.restricted โ€” import๊ฐ€ ์˜ฌ ์ˆ˜ ์—†์Œ
  - persisted_24h: UC ๋ถ€์žฌ 24์‹œ๊ฐ„ ์ง€์† ๋ฐฑ์Šคํ†ฑ (UserDefaults ํƒ€์ž„์Šคํƒฌํ”„)
- checkiCloudAccountStatus()์—์„œ lastKnownAccountStatus ์บ์‹ฑ
- Import ์ข…๋ฃŒ ํ›„ UC๊ฐ€ ์—†์œผ๋ฉด grace ๋งŒ๋ฃŒ ์งํ›„ Path-B ๊ฐฑ์‹  ์žฌํŠธ๋ฆฌ๊ฑฐ
  (scheduleSelfHealRecheckIfNeeded) โ€” ์‚ฌ์šฉ์ž ์กฐ์ž‘ ์—†์ด๋„ UI ๋ณต๊ตฌ
- UC ์ •์ƒ ์กฐํšŒ/์˜๋„์  ์ดˆ๊ธฐํ™” ์‹œ ๋ถ€์žฌ ๊ด€์ธก ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ฆฌ
- Analytics: sync_uc_created path=self_heal + reason ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PDsJ57bUdibeuKL5P72y6f
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

๐Ÿ“ Walkthrough

Walkthrough

Adds a "UC missing self-heal" mechanism to CoreDataStorage that detects when a user collection is persistently missing despite having existed before, tracks observation timestamps and account status, schedules a post-import recheck, and creates a new UC when conditions permit. Documentation updated accordingly.

Changes

UC Missing Self-Heal

Layer / File(s) Summary
Self-heal state and decision logic
Animal-Crossing-Wiki/Projects/App/Sources/CoreDataStorage/CoreDataStorage.swift
Adds UserDefaults-backed observation timestamp tracking, cached iCloud account status, timeout constants, and ucMissingSelfHealReason()/markUCMissingObserved()/clearUCMissingObservation() helpers to determine when self-heal is permitted.
Integration into UC lifecycle and CloudKit import
Animal-Crossing-Wiki/Projects/App/Sources/CoreDataStorage/CoreDataStorage.swift
Clears observation state on flag reset and found UC, schedules scheduleSelfHealRecheckIfNeeded() after successful import, and updates getUserCollection to attempt self-heal UC creation via a new selfHeal UCCreationPath case before falling back to .notFound.
Self-heal documentation
docs/features/icloud-sync.md
Documents the self-heal exception, adds a "UC Missing Self-Heal" section explaining reasons and behavior, and expands analytics event documentation to include the self_heal path.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CloudKit
  participant CoreDataStorage
  participant UserDefaults
  participant NotificationCenter

  CloudKit->>CoreDataStorage: didFinishCloudImport
  CoreDataStorage->>CoreDataStorage: scheduleSelfHealRecheckIfNeeded()
  CoreDataStorage->>NotificationCenter: didReceiveRemoteChanges (after delay)
  NotificationCenter->>CoreDataStorage: getUserCollection triggered
  CoreDataStorage->>CoreDataStorage: ucMissingSelfHealReason()
  CoreDataStorage->>UserDefaults: read/write observation timestamp
  alt reason returned
    CoreDataStorage->>CoreDataStorage: create new UC (selfHeal path)
  else no reason
    CoreDataStorage->>UserDefaults: markUCMissingObserved()
    CoreDataStorage-->>CoreDataStorage: throw .notFound
  end
Loading

Possibly related PRs

  • leeari95/ACNH-wiki#156: Extends the same getUserCollection UC-missing/UC-creation logic in CoreDataStorage.swift and the same docs file that this PR builds upon.
  • leeari95/ACNH-wiki#174: Modifies hasEverHadUserCollection/grace-period suppression logic and the missing-UC path in getUserCollection that this PR extends into self-heal.
  • leeari95/ACNH-wiki#178: Modifies the same getUserCollection UC-missing/UC-creation control flow, adding a related grace-window mechanism alongside this PR's self-heal path.
๐Ÿšฅ Pre-merge checks | โœ… 4 | โŒ 1

โŒ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check โš ๏ธ Warning The description summarizes the fix, but it does not follow the required template and omits the Issue, work log, type, and checklist sections. Rewrite the PR description using the repository template, including the Issue URL, work log, change type, and checklist items.
โœ… Passed checks (4 passed)
Check name Status Explanation
Title check โœ… Passed The title clearly matches the main change: adding self-heal recovery for iCloud UC-missing sync failures.
Docstring Coverage โœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check โœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check โœ… Passed Check skipped because no linked issues were found for this pull request.
โœจ Finishing Touches
๐Ÿ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
๐Ÿงช Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/icloud-sync-broken-awvbx8

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

โค๏ธ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

๐Ÿค– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Animal-Crossing-Wiki/Projects/App/Sources/CoreDataStorage/CoreDataStorage.swift`:
- Around line 190-231: SwiftLint is flagging same-line guard-else returns in the
self-heal helpers, so update the affected `guard` statements in
`markUCMissingObserved`, `clearUCMissingObservation`, and the related
`scheduleSelfHealRecheckIfNeeded` logic to use newline-form `else` blocks that
satisfy `conditional_returns_on_newline`; then run the projectโ€™s SwiftLint
auto-fix flow to apply the formatting consistently.
๐Ÿช„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

โ„น๏ธ Review info
โš™๏ธ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00ad0540-ea8d-4d11-af16-b93f20b2d686

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between d96c712 and e462d6e.

๐Ÿ“’ Files selected for processing (2)
  • Animal-Crossing-Wiki/Projects/App/Sources/CoreDataStorage/CoreDataStorage.swift
  • docs/features/icloud-sync.md

Comment on lines +190 to +231
private func ucMissingSelfHealReason() -> String? {
// ์ด ์„ธ์…˜์—์„œ CloudKit Import๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋๋Š”๋ฐ๋„ UC๊ฐ€ ์—†์Œ.
// grace period(์ฒซ Import ํ›„ 120์ดˆ)๋ฅผ ์ด๋ฏธ ํ†ต๊ณผํ•œ ๋’ค์—๋งŒ ๋„๋‹ฌํ•˜๋ฏ€๋กœ,
// ํ›„์† import batch๋ฅผ ๊ธฐ๋‹ค๋ฆด ๋งŒํผ ๊ธฐ๋‹ค๋ฆฐ ์ƒํƒœ โ†’ ํด๋ผ์šฐ๋“œ์— ๋ณต์›ํ•  UC๊ฐ€ ์—†๋‹ค๊ณ  ํŒ๋‹จ.
if lastSuccessfulImportDate != nil {
return "import_settled"
}
// iCloud ๊ณ„์ •์ด ์—†๊ฑฐ๋‚˜ ์ œํ•œ๋จ โ€” re-import ์ž์ฒด๊ฐ€ ์˜ฌ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ฆ‰์‹œ ํ—ˆ์šฉ.
// (๊ณ„์ • ๋ฏธ๋กœ๊ทธ์ธ ์‹œ NSPersistentCloudKitContainer๊ฐ€ ๋กœ์ปฌ ๋ฏธ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•˜๋Š”
// ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด, ์ด ์ผ€์ด์Šค๋ฅผ ๋ง‰์œผ๋ฉด ๋น„ iCloud ์‚ฌ์šฉ์ž๋Š” ์•ฑ์„ ์•„์˜ˆ ์“ธ ์ˆ˜ ์—†๊ฒŒ ๋จ)
if let status = lastKnownAccountStatus, status == .noAccount || status == .restricted {
return "no_icloud_account"
}
// ๋ฐฑ์Šคํ†ฑ: UC ๋ถ€์žฌ ์ƒํƒœ๊ฐ€ 24์‹œ๊ฐ„ ๋„˜๊ฒŒ ์ง€์† (์˜คํ”„๋ผ์ธ ๋“ฑ ์œ„ ์กฐ๊ฑด์œผ๋กœ ๋ชป ์žก๋Š” ์ผ€์ด์Šค)
let observedAt = UserDefaults.standard.double(forKey: Self.ucMissingFirstObservedAtKey)
if observedAt > 0 {
let elapsed = Date().timeIntervalSince1970 - observedAt
if elapsed > Self.ucMissingSelfHealSeconds {
return "persisted_24h"
}
if elapsed < 0 {
// ์‹œ๊ณ„ ์—ญํ–‰ โ€” ํƒ€์ž„์Šคํƒฌํ”„ ์žฌ๊ธฐ๋ก
markUCMissingObserved(force: true)
}
}
return nil
}

/// UC ๋ถ€์žฌ ์ƒํƒœ๋ฅผ ์ฒ˜์Œ ๊ด€์ธกํ•œ ์‹œ๊ฐ์„ ๊ธฐ๋ก (24์‹œ๊ฐ„ ๋ฐฑ์Šคํ†ฑ ํŒ์ •์šฉ, ์ตœ์ดˆ 1ํšŒ๋งŒ)
private func markUCMissingObserved(force: Bool = false) {
let defaults = UserDefaults.standard
guard force || defaults.double(forKey: Self.ucMissingFirstObservedAtKey) <= 0 else { return }
defaults.set(Date().timeIntervalSince1970, forKey: Self.ucMissingFirstObservedAtKey)
}

/// UC๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์กฐํšŒ๋˜๋ฉด ๋ถ€์žฌ ๊ด€์ธก ๊ธฐ๋ก์„ ์ •๋ฆฌ
private func clearUCMissingObservation() {
let defaults = UserDefaults.standard
guard defaults.object(forKey: Self.ucMissingFirstObservedAtKey) != nil else { return }
defaults.removeObject(forKey: Self.ucMissingFirstObservedAtKey)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ“ Maintainability & Code Quality | ๐ŸŸก Minor | โšก Quick win

Self-heal decision logic looks correct. Backward clock-skew is re-recorded, and each reason gates cleanly before the .notFound fallthrough.

Minor: SwiftLint conditional_returns_on_newline flags the same-line guard โ€ฆ else { return } on Line 221 and Line 228 (and also Line 514/516/518 in scheduleSelfHealRecheckIfNeeded). These are auto-fixable via swiftlint --config .swiftlint.yml --fix.

As per coding guidelines ("SwiftLint ์„ค์ •์— ๋”ฐ๋ผ ๋ฆฐํŠธ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ , ์ž๋™ ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ๋ฌธ์ œ๋Š” swiftlint --config .swiftlint.yml --fix๋กœ ์ฒ˜๋ฆฌ").

๐Ÿงฐ Tools
๐Ÿช› SwiftLint (0.65.0)

[Warning] 221-221: Conditional statements should always return on the next line

(conditional_returns_on_newline)


[Warning] 228-228: Conditional statements should always return on the next line

(conditional_returns_on_newline)

๐Ÿค– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Animal-Crossing-Wiki/Projects/App/Sources/CoreDataStorage/CoreDataStorage.swift`
around lines 190 - 231, SwiftLint is flagging same-line guard-else returns in
the self-heal helpers, so update the affected `guard` statements in
`markUCMissingObserved`, `clearUCMissingObservation`, and the related
`scheduleSelfHealRecheckIfNeeded` logic to use newline-form `else` blocks that
satisfy `conditional_returns_on_newline`; then run the projectโ€™s SwiftLint
auto-fix flow to apply the formatting consistently.

Sources: Coding guidelines, Linters/SAST tools

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.

2 participants