You buy a new MacBook and use Migration Assistant to bring everything over from the old one. On the surface everything looks fine — until you try to edit a single photo and the download progress ring sits at almost-full for several minutes without ever completing.

You just wanted to rotate a photo, but the Rotating photo... progress window hangs forever, and there is no cancel button. Meanwhile you have over 800GB of free disk space and a perfectly healthy connection.

This post is a record of how I traced a frozen iCloud Photos sync immediately after migrating from an M1 MacBook Air to an M5, using system logs. It also includes the two separate times I reached a wrong conclusion. Once I misdiagnosed the root cause entirely, and because of that misdiagnosis I nearly destroyed a batch of photo edits permanently. Honestly, that part was the more valuable lesson.

The new MacBook Air
▲ The new MacBook Air

1. The Symptoms

🚨 Summary of what happened:
  • Context: Migrated M1 → M5 MacBook Air with Migration Assistant. Symptoms appeared the next day while organizing photos.
  • Symptom 1: Entering edit mode in Photos leaves the original-download progress ring stuck near 99% for minutes with no progress.
  • Symptom 2: Rotating a photo shows a Rotating photo... modal that never finishes, with no way to cancel.
  • Common factor: Both actions require the full original of the asset.

For context, this Mac was configured with iCloud Photos + "Optimize Mac Storage," with about 23,000 photos in the library. In that mode the originals live only in iCloud and the Mac keeps downscaled versions, so every edit has to fetch the original on demand. A freshly migrated Mac has essentially no local originals at all. That is where things broke.


2. What It Was NOT (Ruling Things Out First)

"It's slow" has far too many possible causes. So I started by eliminating the usual suspects.

# Free disk space
df -h / /System/Volumes/Data

# iCloud reachability and response time
curl -s -o /dev/null -w "%{http_code} time=%{time_total}s\n" --max-time 10 https://www.icloud.com

# Low Power Mode / thermal throttling
pmset -g | grep -i lowpower
pmset -g therm
CheckResultVerdict
Free disk space824GB availableNot the cause
iCloud connectivity50ms response, healthyNot the cause
Low Data ModeOffNot the cause
Low Power ModeOffNot the cause
Thermal / power throttlingNo warnings recordedNot the cause

Everything looked healthy. When the surface-level metrics tell you nothing, it is time to read the system logs directly.


3. Finding the Real Cause in the System Logs

⚠️ zsh users beware: zsh has a built-in command called log. Typing log show ... directly gives you zsh: too many arguments. You must invoke it by absolute path: /usr/bin/log. This cost me a while at the start, because I thought the logs were simply empty.

3-1. Know the daemons involved

The Photos app does not work alone. Three daemons split the responsibilities.

  • cloudphotod — the iCloud Photos sync engine (CPL). Handles uploads and downloads. This was the culprit.
  • photolibraryd — library database management. Serves asset metadata and resource requests.
  • photoanalysisd — photo analysis. Runs background work such as people and scene recognition.
# Check the state of the Photos-related processes
ps axo pid,%cpu,%mem,etime,comm | grep -Ei "cloudphotod|photolibraryd|photoanalysisd|Photos"

# Read cloudphotod logs (absolute path is required)
/usr/bin/log show --last 15m --predicate 'process == "cloudphotod"' --style compact

3-2. Key clue #1 — the download cache was out of sync

(libcopyfile.dylib) open on ~/Pictures/Photos Library.photoslibrary/resources/cpl/
  cloudsync.noindex/storage/filecache/ASY/cplASYx6wh4RwlgxxUbkaGjn7qILOPl.jpeg:
  No such file or directory

filecache is the staging area where iCloud downloads land before being moved into the library. The daemon was looking for a file that did not exist. Listing that directory confirmed it: zero files.

In other words, the download completed but the final move into the library kept failing. That is precisely the point where the progress ring froze at 99%.

3-3. Key clue #2 — downloads were being cancelled repeatedly

Finished operation ... shortened-name=Resources Download, qos=UserInitiated,
  stateFlags=executing|cancelled
  with error: CKError "Operation Cancelled" (20)

The original-download request issued by the edit view (qos=UserInitiated) was being cancelled over and over instead of completing. And critically, past a certain point there were zero new download requests at all. The progress ring in the UI kept spinning while literally nothing was happening underneath.

3-4. Root cause — the library was rebuilt because of the migration

defaults read com.apple.cloudphotod
lastOSBuildUpgradeDate = 2026-08-16 07:16:45
previousOSBuildVersion = 25F84   →   lastSeenOSBuildVersion = 25G76
previousCPLVersion = CloudPhotoLibrary-851.0.103
lastSeenCPLVersion = CloudPhotoLibrary-860.0.170
previousDBVersion  = 93   →   lastSeenDBVersion = 94
⚠️ Do not let this field name fool you. Because it is called lastOSBuildUpgradeDate, I initially read it as "this Mac was upgraded today." That was wrong. What it actually means is "this library was last opened on build 25F84, and it is now being opened on 25G76." In other words, the library moved to a Mac running a newer build — not that this Mac was upgraded.

Checking the hardware and the migration history makes it unambiguous.

# Check the hardware
system_profiler SPHardwareDataType | grep -iE "Model Identifier|Chip"

# Check whether Migration Assistant ran
/usr/bin/log show --last 24h \
  --predicate 'process CONTAINS "Migration"' --style compact | head
Model Identifier: Mac17,3        Chip: Apple M5

Setup Assistant   [SystemMigration] Will try to obtain Source system
systemmigrationd: Starting
🔍 Diagnosis: The night before, Migration Assistant had moved everything from an M1 to this M5. When the migrated Photos library was opened for the first time on a Mac running a newer build, the sync engine (CPL) migrated from 851 to 860 and the internal DB from 93 to 94. At the same time, an initial full sync — pulling the entire library down from iCloud from scratch — kicked off. During that process the cache state and the actual files drifted apart, and the download queue got trapped in a cancel-and-retry loop before dying entirely.

Seen through this lens, everything that had looked strange earlier suddenly makes sense.

ObservationExplanation under the migration lens
Essentially zero local original filesBrand-new Mac — nothing has been downloaded yet
filecache completely emptyExpected on a fresh machine
Full library re-sync in progressPulling everything down from iCloud from scratch
photoanalysisd re-analyzing everythingPeople and scene recognition running from zero on the new Mac
100–200 seconds to download one photoThe initial sync is consuming most of the bandwidth

4. Fix Step 1 — Restart the Sync Daemon

The first thing to do is revive the stalled cloudphotod. launchd relaunches it automatically, so simply killing it is enough.

osascript -e 'quit app "Photos"'
sleep 3
killall cloudphotod
✅ Result:
  • filecache ... No such file or directory errors dropped to zero
  • The dead queue came back to life — Will download 35 tasks and similar entries resumed
⚠️ Note: photolibraryd ignores SIGTERM. But there is no need to SIGKILL it. It manages the library database, so force-killing it is pure downside — and the cancel loop lived in cloudphotod anyway.

5. Fix Step 2 — Repair the Photos Library

If restarting the daemon is not enough, Repair Library is the proper fix. It rebuilds the database that the migration left inconsistent.

How to run Repair Library
  1. Fully quit the Photos app.
  2. Hold Option(⌥) + Command(⌘) and launch Photos.
  3. When the "Repair Library" dialog appears, click Repair.
⚠️ Common mistake: ⌥⌘-clicking the Dock icon triggers "Hide Others" instead, and the repair dialog never appears. The modifier keys must already be held as the app starts launching for Photos to read them. Hold the keys, double-click Photos in the Applications folder, and keep holding until the dialog appears.
# You can follow the repair progress in the logs
/usr/bin/log show --last 5m --predicate 'process == "photolibraryd"' --style compact \
  | grep -iE "rebuild|Migration"
Asset journal rebuild started
JournalManager[Rebuild]: notified rebuild starting
Asset journal rebuild finished, duration: 69.5s
✅ After the repair: The asset journal rebuild completed in about 70 seconds. The recurring CSU EntityStore load failures disappeared along with it.

6. And Here Is Where I Got It Wrong a Second Time

Right after the repair I tried rotating again — and it hung once more. The logs showed a new error.

resource availability request replying to client with success: N,
  duration: 0.002s,
  error: PHPhotosErrorDomain Code=3164
  resource: type: img-prim, version: adj

version: adj means the rendered version with edits applied. And it failed in 0.002 seconds — an instant "that resource does not exist," without even touching the network. Requests for the original (version: org) never failed once.

So I concluded: "The edited renditions for these photos are permanently lost in iCloud. Repair can't fix that. The only option left is Revert to Original."

🚨 That conclusion was wrong. Had I acted on it and run a bulk revert, I would have permanently destroyed edits that were perfectly intact.

Verification: actually test whether they are really lost

Fortunately I did not run the revert immediately. Instead I first built a small verification tool using PhotoKit that genuinely attempts the fetch. This is the core of it:

let opts = PHImageRequestOptions()
opts.version = .current          // the rendered version with edits applied
opts.isNetworkAccessAllowed = true
opts.deliveryMode = .highQualityFormat

mgr.requestImageDataAndOrientation(for: asset, options: opts) { data, _, _, info in
    // Check whether data actually arrives, or an error comes back
}
⚠️ Gotcha: PHImageManager delivers its result handler on the main queue. In a command-line tool, blocking the main thread on a semaphore starves the run loop, so the callback never arrives. Do the work on a background queue and let the main thread run RunLoop.main.run(). Not knowing this, my first run reported "everything timed out" — a completely bogus result.

Here is the corrected run:

BEBDAE1D-…   OK    0.015s   1,703,918 bytes   ← the very photo that hung on rotate
7B36D4A5-…   OK    0.003s   1,293,741 bytes
20FA7435-…   OK    0.003s   1,324,283 bytes
8C480550-…   OK    0.003s   1,915,673 bytes
E7A0E85C-…   OK    0.002s   1,582,528 bytes
✅ Every single one was fine. Nothing had been lost. The repair had rebuilt the database, but the edited renditions had simply not been re-downloaded yet — a transitional state that I misread as permanent data loss. Meanwhile cloudphotod kept fetching them in the background and the problem resolved on its own.

The logs backed this up: the Code=3164 errors clustered around one specific window and then never appeared again.

💡 Two lessons:
  • Do not take field names at face value. Reading lastOSBuildUpgradeDate as "an OS upgrade" led me to misdiagnose the root cause completely. Checking the hardware model and the migration logs alongside it would have told me the truth from the start.
  • Verify the premise before any irreversible action. An error in the log describes the state at that moment — it is no guarantee that the state is permanent. Transient failures are especially normal during the window right after a migration.

7. Telling "Stuck" Apart from "Just Slow"

Even after everything was fixed, rotations still took time — one photo took 2 minutes 11 seconds. But that is normal behavior, not a fault. Since the visible symptom is identical, you need a way to tell them apart.

SignalGenuinely stuck (broken)Just slow (normal)
cloudphotod CPUPinned at 0%Fluctuating, tens of %
Download request logsZeroWill download keeps appearing
Action completion flagcompleted:0 forevercompleted:1 (tens to hundreds of seconds)
Log errorsfilecache misses, Code=3164None
# The simplest test: look at cloudphotod's CPU
ps axo pid,%cpu,comm | grep cloudphotod | grep -v grep
💡 In practice: Open Activity Monitor and watch cloudphotod. If its CPU is moving at all, it is downloading — just wait. If it is frozen at 0%, it is stuck — restart the daemon.

8. The Real Fix — Stop Waiting Altogether

The root reason every edit costs minutes is the "Optimize Mac Storage" setting. Originals live only in iCloud, so each edit has to fetch one on demand — and a freshly migrated Mac has almost nothing cached locally, which makes the wait especially long.

Photos → Settings (⌘,) → iCloud tab → "Download Originals to this Mac"
  • Right after switching, it downloads every original — expect hours to days and heavy disk usage.
  • In exchange, the wait during edit and rotate disappears entirely.
  • You need free disk space matching your library size. For a library of ~23,000 photos, budget tens of GB or more.
⚠️ On a new Mac, adjust your expectations: Right after a migration, the initial iCloud sync and the photo re-analysis run concurrently. In that state downloads are unusually slow (measured at 100–200 seconds per photo). For a library of ~20,000 photos, the initial sync alone can take days. Rather than rushing into organizing photos, plug the Mac into power and leave it overnight to let the initial sync finish — you will get there far faster.

9. Conclusion and Checklist

💡 Photos sync stuck after a migration — the short version:
  • Rule things out first: disk, network, Low Power Mode, thermal throttling. It is rarely any of these.
  • Read the logs: /usr/bin/log show --predicate 'process == "cloudphotod"' (on zsh, always use the absolute path)
  • Step 1: Quit Photos, then killall cloudphotod. This resolves most cases.
  • Step 2: Hold ⌥⌘ while launching Photos → Repair Library.
  • Never rush: "Revert to Original" cannot be undone. Transient post-migration errors are easy to mistake for permanent loss.
  • The surest approach: On a new Mac, let the initial sync finish. If you edit photos often, switching to "Download Originals to this Mac" is the long-term fix.
⚠️ One last thing: Before doing anything that touches your photo library, verify your Time Machine backup. Run tmutil latestbackup to confirm a recent backup exists. I discovered only afterward that the backup destination had failed to mount during all of this — meaning that if I had acted on my wrong conclusion, there would have been no way back. This matters especially on a freshly migrated Mac, where backups are often not configured yet.

So the next time Photos looks frozen, resist both the urge to wait indefinitely and the urge to reach for a destructive fix. Check the logs first and find out what is actually happening. More often than not, the answer is already written there — though as my two wrong calls in this session showed, reading those logs correctly is a separate skill entirely.