Skip to content

TerraiOS initConnection crash in ClientApp app, but same code works in TerraSample (iOS 26.2.1, v1.6.31) #11

Description

@gokulgovind-hit

TerraiOS initConnection crash in ClientApp, but same code works in TerraSample (iOS 26.2.1, v1.6.31)

Summary

I have two apps in the same repo using the Terra iOS SDK (TerraiOS v1.6.31):

  • TerraSample (minimal sample app)
  • ClientApp (main production app, target: ClientApp)

Both use effectively the same TerraHealthManager implementation and run on the same device, iOS version, Xcode version, and Terra SDK version.

In TerraSample, the Terra connection + HealthKit authorization + daily data fetch work without issues.

In ClientApp, the app crashes inside the Terra / HealthKit authorization path during TerraManager.initConnection(type: .APPLE_HEALTH, ...), even though the capabilities and entitlements are the same.


Environment

  • Device: iPhone (real device)
  • iOS: 26.2.1
  • Xcode: 26.0.1
  • Terra iOS SDK: TerraiOS v1.6.31
  • Targets:
    • Working: TerraSample
    • Crashing: ClientApp (mobile-ios/ClientApp)

Relevant code

TerraHealthManager (ClientApp)

`final class TerraHealthManager: @unchecked Sendable {
static let shared = TerraHealthManager()
private let devId = "
private var terraManager: TerraManager?
private let queue = DispatchQueue(label: "ClientApp.terra.manager")

// Check if a Terra connection exists
var isTerraUserConnected: Bool {
    guard let terraManager else { return false }
    return terraManager.getUserId(type: .APPLE_HEALTH) != nil
}

// Public entry point
func ensureTerraReady() async throws {
    try await initialiseSDKIfNeeded()
    try await ensureUserConnection()
}

private func initialiseSDKIfNeeded() async throws {
    if terraManager != nil { return }

    try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
        queue.async {
            if self.terraManager != nil {
                continuation.resume()
                return
            }
            let referenceId = UUID().uuidString
            Terra.instance(devId: self.devId, referenceId: referenceId) { manager, error in
                if let _ = error {
                    continuation.resume(throwing: TerraHealthError.sdkNotInitialised)
                    return
                }
                guard let manager else {
                    continuation.resume(throwing: TerraHealthError.sdkNotInitialised)
                    return
                }
                self.terraManager = manager
                continuation.resume()
            }
        }
    }
}

private func ensureUserConnection() async throws {
    guard let terraManager else {
        throw TerraHealthError.sdkNotInitialised
    }
    if terraManager.getUserId(type: .APPLE_HEALTH) != nil {
        return
    }

    // Fetch Terra token from our backend (this works; not the cause of crash)
    let token = try await TerraAPI.fetchToken()

    try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
        let activityPermissions: Set<CustomPermissions> = [.ACTIVITY_SUMMARY]
        terraManager.initConnection(
            type: .APPLE_HEALTH,
            token: token,
            customReadTypes: activityPermissions,
            schedulerOn: false
        ) { success, error in
            if let error {
                continuation.resume(throwing: TerraHealthError.initConnectionFailed(error.localizedDescription))
                return
            }
            guard success else {
                continuation.resume(throwing: TerraHealthError.initConnectionFailed("Unsuccessful status"))
                return
            }
            continuation.resume()
        }
    }
}

}`

This logic (same devId, same initConnection call) has been copied into the TerraSample project and works there.

SwiftUI entry point (ClientApp)

`private func requestTerraPermission() {
isLoading = true
errorMessage = nil
isConnected = false
activitySummaryText = nil

Task {
    do {
        try await TerraHealthManager.shared.ensureTerraReady()
        await MainActor.run {
            isLoading = false
            isConnected = TerraHealthManager.shared.isTerraUserConnected
            if isConnected {
                loadActivitySummary()
            }
        }
    } catch {
        await MainActor.run {
            isLoading = false
            errorMessage = error.localizedDescription
        }
    }
}

}`

The TerraSample app uses the same pattern: .onAppear { Task { try await TerraHealthManager.shared.ensureTerraReady() } }.

Entitlements and Info.plist

Both targets now have matching HealthKit entitlements.

TerraSample entitlements:

<!-- TerraSample/TerraSample/TerraSample.entitlements --> <dict> <key>com.apple.developer.healthkit</key> <true/> <key>com.apple.developer.healthkit.access</key> <array> <string>health-records</string> </array> <key>com.apple.developer.healthkit.background-delivery</key> <true/> </dict>

ClientApp entitlements:

<!-- mobile-ios/ClientApp/ClientApp.entitlements --> <dict> <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> <key>com.apple.developer.healthkit</key> <true/> <key>com.apple.developer.healthkit.access</key> <array> <string>health-records</string> </array> <key>com.apple.developer.healthkit.background-delivery</key> <true/> </dict>

Both Info.plist files contain the same Terra background task identifier:

<!-- TerraSample/TerraSample/Info.plist --> <dict> <key>BGTaskSchedulerPermittedIdentifiers</key> <array> <string>co.tryterra.data.post.request</string> </array> </dict>

<!-- mobile-ios/ClientApp/Info.plist --> <dict> <key>BGTaskSchedulerPermittedIdentifiers</key> <array> <string>co.tryterra.data.post.request</string> </array> <!-- ...other app keys (fonts, Google Sign-In, etc.)... --> </dict>

Crash details (ClientApp)

Image

When running the ClientApp target and opening the Terra connect screen:
As soon as terraManager.initConnection(type: .APPLE_HEALTH, ...) executes, the app crashes.
The Xcode thread view shows the crash on a HealthKit queue:
Key frames (from the screenshot stack trace):

Note: There is no Swift error thrown from my code path; instead it’s a low‑level crash due to _dispatch_assert_queue_fail. The console message indicates a queue assertion failure in the HealthKit client implementation.

What I have already verified

  • Same code: TerraHealthManager logic is effectively identical in TerraSample and ClientApp.
  • Same dev ID: "".
  • Same Terra iOS SDK version: TerraiOS v1.6.31.
  • Same iOS version: 26.2.1.
  • Same Xcode version: 26.0.1.
  • Same HealthKit capabilities / entitlements: both have com.apple.developer.healthkit, com.apple.developer.healthkit.access with health-records, and com.apple.developer.healthkit.background-delivery.
  • Dynamic token path works in practice (I can see the JSON response decoding correctly to TerraTokenResponse); ignoring token issues, the crash happens during HealthKit authorization, not while calling my backend.
    Also:
  • I have updated TerraSample to not only connect but also fetch daily step count using Terra; that app continues to work without any crash.

Expected behavior

  • TerraHealthManager.shared.ensureTerraReady() should behave the same way in both targets:
    - Initialize Terra.
    - Request HealthKit authorization via TerraManager.initConnection(...).
    - Either:
    - Complete successfully, or
    - Call the completion handler with success = false and an error.
  • It should not crash due to an internal dispatch queue assertion inside HealthKit / Terra.

Actual behavior

  • TerraSample (working):
    - Authorization UI appears as expected.
    - User can grant permissions.
    - Subsequent getDaily calls run successfully.
  • ClientApp (crashing):
    - When calling TerraHealthManager.shared.ensureTerraReady() from SwiftUI .onAppear using Task { ... }, the app crashes during TerraManager.initConnection with _dispatch_assert_queue_fail on a HealthKit client queue.
    - No Swift error is thrown back to my completion handler; the process just terminates.

Questions for Terra team

  • Are there any target‑specific or project‑specific requirements (beyond HealthKit entitlements and BGTaskSchedulerPermittedIdentifiers) that could cause TerraManager.initConnection → AppleHealth.init → HealthStore.requestAuthorization to assert on a specific dispatch queue in one target but not another, even when:
    - Code is identical,
    - SDK version is identical,
    - Entitlements are identical,
    - Call site is the same pattern (SwiftUI + Task + await ensureTerraReady())?

  • Is there any known issue in TerraiOS v1.6.31 where initConnection/HealthKit authorization is sensitive to being called from a particular queue context in multi‑module apps?

  • Is there a way to enable additional diagnostic logging in TerraiOS (or recommended flags) so I can capture more detailed logs about:
    The precise queue expectations around HealthKit authorization, and
    Why _dispatch_assert_queue_fail is being triggered for the ClientApp target but not for TerraSample?

Any guidance or suggested workarounds (e.g., forcing initConnection to be called on a specific queue / main thread, additional configuration needed, or known fixes in a newer TerraiOS version) would be greatly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions