TipKit crash on launch (iOS 27.2) — swift_unexpectedError with no app frames

We're seeing a crash from a user on iOS 27.2 that we can't reproduce locally. The crash shows swift_unexpectedError on the main thread with no application frames between the concurrency runtime and the crash.

Our app binary hasn't changed since summer 2025 but this user started experiencing the crash for the first time about a week ago.

Thread 0 (crashed):

Thread 0 Crashed (com.apple.main-thread):
0   libswiftCore.dylib            _assertionFailure(_:_:file:line:flags:)
1   libswiftCore.dylib            swift_unexpectedError
2   libswift_Concurrency.dylib    swift::runJobInEstablishedExecutorContext(...)
3   libswift_Concurrency.dylib    swift_job_runImpl(...)
4   libdispatch.dylib             _dispatch_main_queue_drain
5   libdispatch.dylib             _dispatch_main_queue_callback_4CF
6   CoreFoundation                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
...                               (standard UIApplicationMain run loop)

The crash occurs 0.45 seconds after launch. The only other active thread at the time of the crash is Thread 6, which shows TipKit driving SwiftData/CoreData work:

Thread 6:

Thread 6 name: Dispatch queue: NSManagedObjectContext 0x110f99500
 0   CoreFoundation        CFBasicHashGetPtrIndex
 1   CoreFoundation        CFBasicHashCreate
 2   CoreFoundation        CFDictionaryCreateMutable
 3   CoreData              _PFCMT_SetValue
 4   CoreData              -[NSManagedObjectContext _retainedObjectWithID:optionalHandler:withInlineStorage:]
 5   CoreData              -[NSManagedObjectContext objectWithID:]
 6   SwiftData             (SwiftData + 373080)
 7   SwiftData             (SwiftData + 281172)
 8   SwiftData             (SwiftData + 359244)
 9   SwiftData             (SwiftData + 355724)
10   SwiftData             (SwiftData + 359208)
11   CoreData              thunk for @callee_guaranteed () -> (@out A, @error @owned Error)
12   CoreData              partial apply for thunk for @callee_guaranteed () -> (@out A, @error @owned Error)
13   CoreData              NSManagedObjectContext._rethrowsHelper_performAndWait(...)
14   CoreData              thunk for @callee_guaranteed @Sendable () -> ()
15   CoreData              thunk for @escaping @callee_guaranteed @Sendable () -> ()
16   CoreData              developerSubmittedBlockToNSManagedObjectContextPerform
17   libdispatch.dylib     _dispatch_client_callout
18   libdispatch.dylib     _dispatch_lane_barrier_sync_invoke_and_complete
19   CoreData              -[NSManagedObjectContext performBlockAndWait:]
20   CoreData              NSManagedObjectContext.performAndWait(_:)
21   SwiftData             (SwiftData + 258584)
22   SwiftData             (SwiftData + 257920)
23   SwiftData             (SwiftData + 326480)
24   SwiftData             (SwiftData + 259564)
25   SwiftData             (SwiftData + 56292)
26   SwiftData             (SwiftData + 385736)
27   TipKitCore            (TipKitCore + 202216)
28   TipKitCore            (TipKitCore + 128232)
29   TipKitCore            (TipKitCore + 124104)
30   TipKitCore            (TipKitCore + 130048)
31   TipKitCore            (TipKitCore + 241553)
32   TipKitCore            (TipKitCore + 152365)
33   TipKitCore            (TipKitCore + 134441)
34   TipKitCore            (TipKitCore + 152629)
35   TipKitCore            (TipKitCore + 134233)
36   TipKitCore            (TipKitCore + 134441)
37   TipKitCore            (TipKitCore + 138237)
38   TipKitCore            (TipKitCore + 134233)
39   libswift_Concurrency  completeTask(swift::AsyncContext*, swift::SwiftError*)

All other threads are idle.

Context:

  • We call try? Tips.configure([.displayFrequency(.hourly)]) early in our startup sequence. The try? should swallow any error from configure itself, but I'm not sure if TipKit spawns its own internal async tasks.
  • Our app uses UIKit (not SwiftUI App lifecycle). We do not use SwiftData directly — any SwiftData/CoreData frames come from system frameworks.
  • The binary hasn't changed since summer 2025, so this appears to be an OS-level regression on iOS 27.x.
  • We cannot reproduce locally.

Questions:

  1. Is there a way to get async backtraces in crash reports from the field, or is this only possible under the debugger?
  2. If TipKit spawns an internal Task that throws into a non-throwing context, would that produce exactly this crash pattern — swift_unexpectedError on the main thread with no third-party frames?
  3. Are there known issues with TipKit's internal SwiftData tasks crashing on iOS 27.x?

Any guidance on narrowing this down without a local repro would be appreciated.

Please post a full Apple crash report for this. See Posting a Crash Report for advice on how to do that.

ps If you have a JSON crash report (.ips) that’d be ideal, but a human readable crash report (.crash) is better than nothing.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi @DTS Engineer thanks for the quick reply and the guide on posting crash reports. I've attached an IPS here (I just replaced sensitive information with XYZXYZ).

Please let me know if I can provide anything else.

tipkit crash adf.ips.txt(21 KB)

Thanks for that crash report.

This is going to be tricky to debug. And, yeah, I agree that this is most likely not your fault, but let’s dive in regardless.

The crashing thread backtrace looks like this:

0 libswiftCore.dylib         … _assertionFailure(_:_:file:line:flags:) + 172
1 libswiftCore.dylib         … swift_unexpectedError + 688
2 libswift_Concurrency.dylib … swift::runJobInEstablishedExecutorContext(swift::Job*, swift::SerialExecutorRef, swift::TaskExecutorRef) + 328
3 libswift_Concurrency.dylib … swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 156
4 libdispatch.dylib          … _dispatch_main_queue_drain + 512
5 libdispatch.dylib          … _dispatch_main_queue_callback_4CF + 44
6 CoreFoundation             … __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16

Frames 4 and above are standard runloop stuff, where the main thread’s run loop is servicing the main queue. Frames 3 and 2 are the Swift runtime dispatching jobs, presumably jobs associated with the main actor and hence running on the main queue. Frame 1 is this code which, as noted in the doc comment, is called by Swift when a try! fails.

So, what’s triggering that. The most obvious candidate is frame 2. However, I don’t think that’s actually the case. Consider it’s code here. It basically calls a function member (ResumeTask) passing it a data member as a parameter (ResumeContext). I can’t see any way that could end up in swift_unexpectedError.

More likely is that it’s actually called out to the job, which has started running Swift code, which has hit a try!, which has ended up tail calling into swift_unexpectedError. But I don’t see any good way of finding more info about that.

If you could reproduce this locally you could try to see what info gets logged by the assert (frame 0), which might contain a valuable clue. But that’s not an option because you can’t reproduce this.


Are you only seeing one user with this crash? It sounds they can reproduce the crash with some degree of reliability. Is that right? Have you captured multiple crash reports from them? If so, do you always see the same pattern, where there’s another thread deep in SwiftData courtesy of TipKit?

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for investigating, Quinn! To answer your questions:

  1. Yes, only one user has seen this crash so far.
  2. They see it consistently - unfortunately, the app crashes on open :(
  3. We have two crash reports from this person. Now that you mention it, the second crash is actually slightly different - IPS included below. The main thread stacktrace is the same, but the other frame shows "UIKit.inProcessAnimationManager".

second crash adf.ips.txt(18 KB)

One other question: In my research, I saw that some people have seen TipKit crashes if they call Tip#shouldDisplayUpdates before Tips.configure. That shouldn't happen in my code but maybe in some weird timing situation it might. Could that be my issue? Then again, it only affects one user.