Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix background crash in Coach BGTask launch handler - #24

Merged
saksham2001 merged 2 commits into
saksham2001:mainfrom
hoveeman:fix/coach-bgtask-background-crash
Jun 28, 2026
Merged

saksham2001 merged 2 commits into
saksham2001:mainfrom
hoveeman:fix/coach-bgtask-background-crash

Conversation

@hoveeman

Copy link
Copy Markdown
Contributor

What

Fixes a periodic background crash (EXC_BREAKPOINT / SIGTRAP) in the Coach daily check-in background task.

CoachNotificationScheduler.register(...) registers the BGTask launch handler with MainActor.assumeIsolated { ... }, on the assumption that BGTaskScheduler.register(forTaskWithIdentifier:using:) with queue: nil runs the handler on the main queue.

It does not — nil means a private background queue. assumeIsolated is a precondition assertion (not a thread hop): when the handler runs off the main actor, the assertion fails and Swift traps, crashing the app every time iOS opportunistically fires the com.pulseloop.coach.refresh task in the background.

Fix

Hop to the main actor explicitly instead of asserting we're already on it:

BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.taskIdentifier, using: nil) { task in
    Task { @MainActor in Self.shared.handle(task) }
}

handle(_:) and the @MainActor-isolated state it touches now genuinely run on the main actor. The async hop is well within the BGTask runtime budget; scheduleNext() / setTaskCompleted behavior is unchanged.

How it was found

Captured in a TestFlight crash report. The crashing thread was a libdispatch background worker:

Thread 5 Crashed:
0  libdispatch.dylib   _dispatch_assert_queue_fail
3  libswift_Concurrency.dylib  _swift_task_checkIsolatedSwift
5  PulseLoop  MainActor.assumeIsolated<A>(_:file:line:)
6  PulseLoop  closure #1 in CoachNotificationScheduler.register(serviceProvider:)  (CoachNotificationScheduler.swift:19)
7  PulseLoop  thunk for @escaping @callee_guaranteed (BGTask) -> ()
8  BackgroundTasks  __41-[BGTaskScheduler _runTask:registration:]_block_invoke_5

The symptom matched exactly: crashes only in the background, never during active use.

Testing

  • Clean Debug simulator build (iPhone 17 Pro, iOS 26.5).
  • The handler can be exercised on a debug build via LLDB:
    e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.pulseloop.coach.refresh"]

🤖 Generated with Claude Code

BGTaskScheduler.register(using: nil) runs its launch handler on a private
background queue, not the main queue. The handler called MainActor.assumeIsolated,
which asserts the current executor is already the main actor and traps
(EXC_BREAKPOINT/SIGTRAP) when it isn't — crashing every time iOS fired the
coach refresh task in the background.

Hop to the main actor with Task { @mainactor in ... } instead of asserting.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@saksham2001
saksham2001 merged commit be6274f into saksham2001:main Jun 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants