Skip to content
Development

An App Store rejection, a MusicKit gotcha, and work I thought I'd lost

By Victor Da Luz
iosswiftmusickitapp-storedev-logdeep-cut-atlas

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 “Unlock 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 unlock button stayed tappable even when the in-app purchase product hadn’t loaded, so a nil product meant an inevitable error alert on tap. Straightforward 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