An App Store rejection, a MusicKit gotcha, and work I thought I'd lost
Deep Cut Atlas got its first App Store rejection this week, and it turned into a two-part lesson: one about MusicKit’s API surface, and one about my own git hygiene.
The rejection
Apple’s reviewer hit two bugs. First, History and Discover both showed the raw string “Privacy acknowledgement required” instead of any real UI. Second, tapping “Enable lifetime Pro” in Settings threw an error alert.
The root cause of the first bug is a distinction I hadn’t thought hard enough about: MusicKit’s authorization status only tells you whether the user granted your app permission to touch their library. It says nothing about whether their account actually has an active Apple Music subscription. My app gated on authorization and stopped there, so a reviewer who granted access but had no subscription sailed straight through the gate and then hit a raw MusicKit error on the very next request. The fix is a second, separate check via MusicSubscription.current, with its own dedicated screen instead of three tabs each surfacing their own flavor of the same underlying problem.
The second bug was simpler: the Pro purchase button stayed tappable even when the in-app purchase product hadn’t loaded, so a nil product meant an inevitable error alert on tap. Simple fix, hide the button until the product is actually there.
Getting the error type right
The specific error text Apple’s reviewer saw (“Privacy acknowledgement required”) comes from a real Swift error case, but guessing its exact type from memory was a bad idea. I was fairly confident it lived on MusicSubscription.Error, which turns out not to be a real type at all, or at least not one I could find documented. A few searches later I confirmed the actual type is MusicTokenRequestError.privacyAcknowledgementRequired. Worth the extra five minutes: shipping a catch clause that matches a case that doesn’t exist means it silently never fires, and you’ve fixed nothing.
The part where I thought the work was gone
Here’s the part I’m less proud of. A prior working session had apparently already built this exact fix, “complete, simulator-verified,” left as an uncommitted note in the tracking ticket. When I went to resume the work, I grepped the whole codebase and every branch for the classes that comment described. Nothing. I concluded the work was lost, uncommitted changes that never got saved before a branch switch, and rebuilt the whole thing from the ticket’s notes.
It wasn’t lost. It was sitting in git stash, which I never thought to check. A stash doesn’t show up in git branch, doesn’t show up in git log, and a working-tree grep can’t see it either, so my search had a real blind spot I didn’t know about until I stumbled onto it later, while doing unrelated branch cleanup.
The silver lining: diffing the two versions, my rebuild caught a real bug the original had, that wrong MusicSubscription.Error type. So the redundant work wasn’t wasted. But “unrecoverable” was a claim I made with more confidence than I’d actually earned, and I want to remember that the next time something looks gone.
What’s next
The code fix is done, tested, and merged. What’s left is entirely on me: confirm in App Store Connect that the in-app purchase is actually attached to the version submission (probably the whole second bug), then bump the build and resubmit.
Related reading
Building a rating prompt I was sure wouldn't render in the simulator
A three-wins gate, one requestReview() call, and a piece of received wisdom about simulator behavior that turned out to be wrong when actually tried.
The playlist that already had the right name
A rename artifact, an API with no author field to update, and a device test proving the bug had already fixed itself - closed as accept-as-is.
Featured albums, real MusicKit data, and a test that lied about passing
There's no isEssential flag - Essentials is a song playlist. featuredAlbums is the real signal, and a fixture change exposed a test nobody had re-run.
You might also find useful
NordPass
Password manager from the team behind NordVPN, with a free tier.
As a NordPass affiliate, I earn from qualifying purchases.
Learn moreAdGuard for iOS
System-wide ad and tracker blocking on iOS, no separate DNS server required.
As an AdGuard affiliate, I earn from qualifying purchases.
Learn moreProton Drive
Encrypted cloud storage from the team behind Proton Mail.
As a Proton Partner, I earn from qualifying purchases of Proton's privacy and security services (Pass, Mail, VPN, Drive).
Learn more