Use for symptom-based background task troubleshooting: task never runs, terminates early, delegate not called, dev/prod mismatch, inconsistent scheduling, launch crash, duplicate runs.
Install
npx skillscat add derklinke/codex-config/ios-background-processing-diag Install via the SkillsCat registry.
SKILL.md
Background Processing Diagnostics
Symptom-first diagnostics for BGTaskScheduler and background URLSession behavior.
Related: ios-background-processing, ios-background-processing-ref.
Symptom 1: Task Never Runs
5-min triage
- Info.plist
BGTaskSchedulerPermittedIdentifiersincludes exact identifier (case-sensitive)UIBackgroundModescontains needed mode (fetch/processing)
- Registration timing
register(...)indidFinishLaunchingWithOptions- registration occurs before first
submit()
- App/system state
- app not force-quit from app switcher
- Background App Refresh enabled
Time-cost guidance
| Approach | Time | Success |
|---|---|---|
| plist + registration audit | ~5 min | high for common failures |
| add logs | ~15 min | higher confidence |
| LLDB simulate launch | ~5 min | highest for handler wiring |
| random code edits | high | low |
LLDB launch test
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]- Breakpoint hits: wiring is correct; issue is scheduling constraints.
- No hit: registration path broken.
Symptom 2: Task Terminates Early
5-min triage
- Expiration handler set first line in handler.
setTaskCompleted(success:)called in all paths (success/failure/expiration).- Task duration within budget:
BGAppRefreshTask: short work only- long work: checkpoint/chunk or move to
BGProcessingTask
Common causes
| Cause | Fix |
|---|---|
| no expiration handler | add immediately |
| missing completion call | call in every path |
| task too long | chunk/checkpoint |
| long network timeout | prefer background URLSession |
| async callback after expiration | gate work with cancellation flag |
LLDB expiration test
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.yourapp.refresh"]Symptom 3: Background URLSession Delegate Not Called
5-min triage
- Session config must be
URLSessionConfiguration.background(...) - Unique session identifier
sessionSendsLaunchEvents = true- App delegate handler implemented and completion handler retained
- Delegate still alive when events complete
Required AppDelegate hooks
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}Symptom 4: Works in Dev, Fails in Production
10-min triage
- System constraints
- low power mode
- background refresh disabled
- low battery discretionary throttling
- App state
- force-quit disables background execution until next manual launch
- low app usage lowers scheduling priority
- Build differences
- debug/release behavior
#if DEBUGdivergence- release bundle identifier mismatch
- Add production telemetry
- scheduled / started / completed / failed events
Scheduling factors to check
- critically low battery
- Low Power Mode
- user app usage frequency
- app switcher force-quit
- Background App Refresh setting
- system background budgets
- task rate limiting
Symptom 5: Inconsistent Scheduling
5-min triage
earliestBeginDateis lower bound, not execution time guarantee.- Avoid setting dates too far out.
- Avoid duplicate scheduling; inspect pending requests.
- Re-schedule in handler for recurring behavior.
- Understand expected behavior:
BGAppRefreshTask: usage-predictive windowsBGProcessingTask: typically charging + idle windows
Symptom 6: Crash on Background Launch
5-min triage
- Launch path safety
- no UI assumptions in background launch
- avoid force unwraps in background-only paths
- handle file protection state (device locked)
- Handler safety
- weak capture where appropriate
- no UI work off main
- Data protection
- ensure files needed in background are accessible (
...UntilFirstUserAuthenticationetc.)
Safe handler skeleton
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.refresh", using: nil) { [weak self] task in
guard let self else {
task.setTaskCompleted(success: false)
return
}
self.performBackgroundWork(task: task)
}Symptom 7: Task Runs Multiple Times
5-min triage
- Avoid scheduling from multiple call sites.
- Use unique identifiers per task purpose.
- Check pending requests before submit.
- Ensure
setTaskCompletedis called promptly.
Duplicate-prevention pattern
func scheduleRefreshIfNeeded() {
BGTaskScheduler.shared.getPendingTaskRequests { requests in
let exists = requests.contains { $0.identifier == "com.app.refresh" }
if !exists { self.scheduleRefresh() }
}
}Quick Checklist
30 seconds
- identifier exists in plist
- registration in launch path
- app not force-quit
5 minutes
- identifier exact match
- background mode enabled
- completion called in all paths
- expiration handler set first
15 minutes
- LLDB simulated launch succeeds
- LLDB simulated expiration handled
- console logs show register -> schedule -> start -> complete
- tested on real device + release build
- Background App Refresh enabled during test
Logs
Filter:
subsystem:com.apple.backgroundtaskscheduler
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"Expected sequence:
- registered handler
- scheduled task
- started task
- app work
- task completed
Resources
- WWDC: 2019-707, 2020-10063
- Skills:
ios-background-processing,ios-background-processing-ref - Platforms: iOS 13+
- Last Updated: 2025-12-31