Skip to content
Development

I recovered a stashed fix, then a live repro on my phone showed me a different bug

By Victor Da Luz
iosswiftdebuggingdev-logdeep-cut-atlas

The Playlist tab in Deep Cut Atlas had an intermittent bug: sometimes, on launch, it would get stuck on “Loading playlist…” forever. No error, no retry button, nothing to do but force-quit. A previous session had already diagnosed and fixed it, tests passing and everything, then paused mid-verification and stashed the work. Today I picked it back up, and it turned into two separate lessons.

Recovering the fix

Applying a two-day-old stash against a codebase that’s had several commits land since is exactly the kind of thing I expect to be painful. It wasn’t. git stash apply did a clean three-way merge across all three touched files, including one file I’d modified for an unrelated fix earlier the same day. Worth remembering: a stash isn’t a snapshot frozen against one specific commit, it merges the same way a branch does.

A linter that can’t see what you can see

The fix itself adds an in-flight request de-duplication guard, so two concurrent callers asking for the same playlist’s contents share one request instead of racing two. SwiftLint immediately flagged it: “errors thrown inside this task are not handled.” Except they are. The result is captured, stored, and awaited two lines later with proper error propagation. I compared it to two other functions in the same file doing the identical pattern that weren’t flagged, and the difference was academic: those use Task.detached, this one uses a plain Task { }. The rule apparently pattern-matches on the initializer syntax rather than tracing whether the result actually gets consumed. I could have restructured the code to dodge it, but that would have meant claiming a concurrency benefit (running detached, off the main actor) that doesn’t even apply here. A scoped, explained disable comment was the more honest fix.

The live repro that wasn’t the bug I was looking for

Here’s the part that actually mattered. Scripted verification wasn’t available for this one - my device UI automation path was down (a WebDriverAgent dependency timed out, unrelated to this issue), and scripted device launches didn’t surface the app’s console output either. So I did the phone-in-hand pass myself.

What I saw: stuck. But not just Playlist. History showed no album artwork. Discover was stuck on its own loading state. All three, at once.

My first instinct was mild panic, did the fix not work? But the bug I’d fixed was narrow and specific: a race between two particular async calls both wanting the same playlist’s data. It never touched Discover’s code at all. Something stuck across three unrelated tabs simultaneously pointed at a different, bigger problem, not a refutation of the narrow one.

A few checks ruled things out fast. The app process was still alive, not crashed. Wifi was fine. The real Apple Music app worked perfectly on the same phone, same network, same moment. And no hang notification fired, which turned out to be its own useful clue rather than a dead end: iOS’s hang detector specifically watches the main thread, so its silence suggested the UI itself wasn’t frozen, something async was just never finishing.

I don’t have a stack trace. Getting one needs Xcode’s debugger attached live. So rather than guess at a root cause I can’t see, I split it into its own investigation and shipped the fix I could actually verify on its own terms: tested, code-reviewed, pattern-matched against an identical mechanism already running in production elsewhere in the same file. The bug that showed up on the phone is real and needs real profiling, not a guess bolted onto an unrelated fix because it happened to show up during the same testing session.

What’s next

The narrow fix is merged. The broader stall (all three tabs, no crash, no hang notification, real Apple Music unaffected) is its own open investigation now, waiting on an Instruments session or a live-attached debugger to actually see where the async work is getting stuck.

Related reading