Skip to content

Swift async task terminates app: swift_unexpectedError in HealthKit query path (v14.0.2, iOS 26.5.2) #366

Description

@Loffretto

Summary

App terminates with EXC_BREAKPOINT (SIGTRAP) triggered by swift_unexpectedError
during (very likely) a HealthKit query invocation. Reproduced on two devices
(iPhone 15, iPhone 17), affects at least library versions consumed in our builds
across multiple weeks — the crash signature has been stable.

swift_unexpectedError indicates a Swift async task threw an Error that
wasn't declared/awaited as throws in the caller chain, so the runtime traps.
No custom Swift code in our app — the only Swift-based native module in our
stack is @kingstinct/react-native-healthkit.

Environment

  • @kingstinct/react-native-healthkit: 14.0.2 (npm latest)
  • React Native: 0.81.x
  • Expo SDK 54
  • Nitro Modules: 0.35.x
  • iOS: 26.5.2 (23F84)
  • Devices: iPhone 15 (iPhone15,4), iPhone 17 (iPhone18,3)
  • Architecture: arm64 / arm64e
  • Distribution: TestFlight

Trigger

User action that reliably precedes the crash:

  • Pull-to-refresh on a screen that runs a HealthKit-import flow, OR
  • Explicit "Import from Apple Health" button.

Both entry points funnel into the same code path which calls, per workout:

queryWorkoutSamples({ filter: { date: { startDate, strictStartDate: true } }, limit: 0, ascending: true })

followed for each returned WorkoutProxy by:

workout.getWorkoutRoutes()                         // HKWorkoutRoute
queryStatisticsForQuantity('HKQuantityTypeIdentifierHeartRate', ['discreteAverage','discreteMax'], { from, to, unit: 'count/min' })
queryStatisticsForQuantity('HKQuantityTypeIdentifierBasalEnergyBurned', ['cumulativeSum'], { from, to, unit: 'kcal' })

App uptime at crash: ~100 seconds — consistent with the auto-import kicking off
during app-start plus a user-triggered second import.

Crash Log (Thread 3, crashed)

Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000184cc3800
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Triggered by Thread: 3
Thread 3 Crashed:
0 libswiftCore.dylib assertionFailure(:_:file:line:flags:) + 168 AssertCommon.swift:171
1 libswiftCore.dylib swift_unexpectedError + 688 ErrorType.swift:216
2 libswift_Concurrency.dylib swift::runJobInEstablishedExecutorContext(Job*) Actor.cpp:243
3 libswift_Concurrency.dylib swift_job_runImpl(Job*, SerialExecutorRef) Actor.cpp:2222
4 libdispatch.dylib _dispatch_root_queue_drain + 360 queue.c:7473
5 libdispatch.dylib _dispatch_worker_thread2 + 184 queue.c:7551
6 libsystem_pthread.dylib _pthread_wqthread + 232
7 libsystem_pthread.dylib start_wqthread + 8
Full .crash files (2 different devices, same signature) available on request.

Suspected cause

An async throws function inside the Swift module is invoked from a Task { ... }
(or a Task.detached { ... }) whose closure isn't declared to propagate the error.
When a specific workout hits an edge case in the query pipeline (permission
denied for a subtype, missing route metadata, or an invalid HKQuantityStatistic
option combination), the throw hits swift_unexpectedError and the process is
terminated.

Common pattern that produces exactly this stack:

Task {                          // <- no `Task<Void, Error>`, no do/catch
    try await someHealthKitCall()   // throws → swift_unexpectedError → crash
}

Reproduction environment

  • Real device, no simulator.
  • User has ~30 days of Apple Health workouts imported prior.
  • Trigger the import path multiple times in short succession (auto-import at
    app start + manual pull-to-refresh 30-90s later).
  • On our end this hits reliably enough that we have multiple TestFlight
    crashlogs across weeks with identical signature.

Ask

  • Is there a known place in the Swift wrapper where an async throw could
    escape without a surrounding Task error handler? Especially in the
    workout-route or per-workout statistics paths.
  • Any interim workaround (call ordering, option adjustments, or a way to
    detect the offending workout) appreciated. Happy to run instrumented
    builds or share more detailed logs.

Thanks for maintaining this library — it's otherwise been rock solid for us.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions