axiom-background-processing-diag
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-background-processing-diag
Agent 安装分布
Skill 文档
Background Processing Diagnostics
Symptom-based troubleshooting for background task issues.
Related skills: axiom-background-processing (patterns, checklists), axiom-background-processing-ref (API reference)
Symptom 1: Task Never Runs
Handler never called despite successful submit().
Quick Diagnosis (5 minutes)
Task never runs?
â
ââ Step 1: Check Info.plist (2 min)
â ââ BGTaskSchedulerPermittedIdentifiers contains EXACT identifier?
â â ââ NO â Add identifier, rebuild
â ââ UIBackgroundModes includes "fetch" or "processing"?
â â ââ NO â Add required mode
â ââ Identifiers case-sensitive match code?
â ââ NO â Fix typo, rebuild
â
ââ Step 2: Check registration timing (2 min)
â ââ Registered in didFinishLaunchingWithOptions?
â â ââ NO â Move registration before return true
â ââ Registration before first submit()?
â ââ NO â Ensure register() precedes submit()
â
ââ Step 3: Check app state (1 min)
ââ App swiped away from App Switcher?
â ââ YES â No background until user opens app
ââ Background App Refresh disabled in Settings?
ââ YES â Enable or inform user
Time-Cost Analysis
| Approach | Time | Success Rate |
|---|---|---|
| Check Info.plist + registration | 5 min | 70% (catches most issues) |
| Add console logging | 15 min | 90% |
| LLDB simulate launch | 5 min | 95% (confirms handler works) |
| Random code changes | 2+ hours | Low |
LLDB Quick Test
Verify handler is correctly registered:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
If breakpoint hits â Registration correct, issue is scheduling/system factors. If nothing happens â Registration broken.
Symptom 2: Task Terminates Unexpectedly
Handler called but work doesn’t complete before termination.
Quick Diagnosis (5 minutes)
Task terminates early?
â
ââ Step 1: Check expiration handler (1 min)
â ââ Expiration handler set FIRST in handler?
â â ââ NO â Move to very first line
â ââ Expiration handler actually cancels work?
â ââ NO â Add cancellation logic
â
ââ Step 2: Check setTaskCompleted (2 min)
â ââ Called in success path?
â ââ Called in failure path?
â ââ Called after expiration?
â ââ ANY path missing â Task never signals completion
â
ââ Step 3: Check work duration (2 min)
â ââ BGAppRefreshTask work > 30 seconds?
â â ââ YES â Chunk work or use BGProcessingTask
â ââ BGProcessingTask work > system limit?
â ââ YES â Save progress, resume on next launch
Common Causes
| Cause | Fix |
|---|---|
| Missing expiration handler | Set handler as first line |
| setTaskCompleted not called | Add to ALL code paths |
| Work takes too long | Chunk and checkpoint |
| Network timeout > task time | Use background URLSession |
| Async callback after expiration | Check shouldContinue flag |
Test Expiration Handling
// First simulate launch
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.yourapp.refresh"]
// Then force expiration
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.yourapp.refresh"]
Verify expiration handler runs and work stops gracefully.
Symptom 3: Background URLSession Delegate Not Called
Download completes but didFinishDownloadingTo never fires.
Quick Diagnosis (5 minutes)
URLSession delegate not called?
â
ââ Step 1: Check session configuration (2 min)
â ââ Using URLSessionConfiguration.background()?
â â ââ NO â Must use background config
â ââ Session identifier unique?
â â ââ NO â Use unique bundle-prefixed ID
â ââ sessionSendsLaunchEvents = true?
â ââ NO â Set for app relaunch on completion
â
ââ Step 2: Check AppDelegate handler (2 min)
â ââ handleEventsForBackgroundURLSession implemented?
â â ââ NO â Required for session events
â ââ Completion handler stored and called later?
â ââ NO â Store handler, call after events processed
â
ââ Step 3: Check delegate assignment (1 min)
ââ Session created with delegate?
ââ Delegate not nil when task completes?
Required AppDelegate Code
// Store completion handler
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
// Call after all events processed
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.backgroundSessionCompletionHandler?()
self.backgroundSessionCompletionHandler = nil
}
}
Symptom 4: Works in Development, Not Production
Task runs with debugger but fails in release builds or for users.
Quick Diagnosis (10 minutes)
Works in dev, not prod?
â
ââ Step 1: Check system constraints (3 min)
â ââ Low Power Mode enabled?
â â ââ Check ProcessInfo.isLowPowerModeEnabled
â ââ Background App Refresh disabled?
â â ââ Check UIApplication.backgroundRefreshStatus
â ââ Battery < 20%?
â ââ System pauses discretionary work
â
ââ Step 2: Check app state (2 min)
â ââ App force-quit from App Switcher?
â â ââ YES â No background until foreground launch
â ââ App recently used?
â ââ Rarely used apps get lower priority
â
ââ Step 3: Check build differences (3 min)
â ââ Debug vs Release optimization differences?
â ââ #if DEBUG code excluding production?
â ââ Different bundle identifier in release?
â
ââ Step 4: Add production logging (2 min)
ââ Log task schedule/launch/complete to analytics
The 7 Scheduling Factors
All affect task execution in production:
| Factor | Check |
|---|---|
| Critically Low Battery | Battery < 20%? |
| Low Power Mode | ProcessInfo.isLowPowerModeEnabled |
| App Usage | User opens app frequently? |
| App Switcher | App NOT swiped away? |
| Background App Refresh | Settings enabled? |
| System Budgets | Many recent background launches? |
| Rate Limiting | Requests too frequent? |
Production Debugging
Add logging to track what’s happening:
func scheduleRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
do {
try BGTaskScheduler.shared.submit(request)
Analytics.log("background_task_scheduled")
} catch {
Analytics.log("background_task_schedule_failed", error: error)
}
}
func handleRefresh(task: BGAppRefreshTask) {
Analytics.log("background_task_started")
// ... work ...
Analytics.log("background_task_completed")
task.setTaskCompleted(success: true)
}
Symptom 5: Inconsistent Task Scheduling
Task runs sometimes but not predictably.
Quick Diagnosis (5 minutes)
Inconsistent scheduling?
â
ââ Step 1: Understand earliestBeginDate (2 min)
â ââ This is MINIMUM delay, not scheduled time
â â ââ System runs when convenient AFTER this date
â ââ Set too far in future (> 1 week)?
â ââ System may skip task entirely
â
ââ Step 2: Check scheduling pattern (2 min)
â ââ Scheduling same task multiple times?
â â ââ Call getPendingTaskRequests to check
â ââ Scheduling in handler for continuity?
â ââ Required for continuous refresh
â
ââ Step 3: Understand system behavior (1 min)
ââ BGAppRefreshTask runs based on USER patterns
â ââ User rarely opens app = rare runs
ââ BGProcessingTask runs when charging
ââ User doesn't charge overnight = no runs
Expected Behavior
| Task Type | Scheduling Behavior |
|---|---|
| BGAppRefreshTask | Runs before predicted app usage times |
| BGProcessingTask | Runs when charging + idle (typically overnight) |
| Silent Push | Rate-limited; 14 pushes may = 7 launches |
Key insight: You request a time window. System decides when (or if) to run.
Symptom 6: App Crashes on Background Launch
App crashes when launched by system for background task.
Quick Diagnosis (5 minutes)
Crash on background launch?
â
ââ Step 1: Check launch initialization (2 min)
â ââ UI setup before task handler?
â â ââ Background launch may not have UI context
â ââ Accessing files before first unlock?
â â ââ Use completeUntilFirstUserAuthentication protection
â ââ Force unwrapping optionals that may be nil?
â ââ Guard against nil in background context
â
ââ Step 2: Check handler safety (2 min)
â ââ Handler captures self strongly?
â â ââ Use [weak self] to prevent retain cycles
â ââ Handler accesses UI on non-main thread?
â ââ Dispatch UI work to main queue
â
ââ Step 3: Check data protection (1 min)
ââ Files accessible when device locked?
ââ Use .completeUnlessOpen or .completeUntilFirstUserAuthentication
File Protection for Background Tasks
// Set appropriate protection when creating files
try data.write(to: url, options: .completeFileProtectionUntilFirstUserAuthentication)
// Or configure in entitlements for entire app
Safe Handler Pattern
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { [weak self] task in
guard let self = self else {
task.setTaskCompleted(success: false)
return
}
// Don't access UI
// Use background-safe APIs only
self.performBackgroundWork(task: task)
}
Symptom 7: Task Runs Multiple Times
Same task appears to run repeatedly or in parallel.
Quick Diagnosis (5 minutes)
Task runs multiple times?
â
ââ Step 1: Check scheduling logic (2 min)
â ââ Scheduling on every app launch?
â â ââ Check getPendingTaskRequests first
â ââ Scheduling in handler AND elsewhere?
â â ââ Consolidate to single location
â ââ Using same identifier for different purposes?
â ââ Use unique identifiers per task type
â
ââ Step 2: Check for duplicate submissions (2 min)
â ââ Multiple submit() calls queued?
â ââ System may batch into single execution
â
ââ Step 3: Check handler execution (1 min)
ââ setTaskCompleted called promptly?
ââ Delay may cause system to think task hung
Prevent Duplicate Scheduling
func scheduleRefreshIfNeeded() {
BGTaskScheduler.shared.getPendingTaskRequests { requests in
let alreadyScheduled = requests.contains {
$0.identifier == "com.app.refresh"
}
if !alreadyScheduled {
self.scheduleRefresh()
}
}
}
Quick Diagnostic Checklist
30-Second Check
- Info.plist has identifier?
- Registration in didFinishLaunchingWithOptions?
- App not swiped away?
5-Minute Check
- Identifiers exactly match (case-sensitive)?
- Background mode enabled (fetch/processing)?
- setTaskCompleted called in all paths?
- Expiration handler set first?
15-Minute Investigation
- LLDB simulate launch works?
- LLDB simulate expiration handled?
- Console shows registration/scheduling logs?
- Real device (not just simulator)?
- Release build (not just debug)?
- Background App Refresh enabled in Settings?
Console Log Filters
// All background task events
subsystem:com.apple.backgroundtaskscheduler
// Specific to your app
subsystem:com.apple.backgroundtaskscheduler message:"com.yourapp"
Expected Log Sequence
- “Registered handler for task with identifier”
- “Scheduling task with identifier”
- “Starting task with identifier”
- (your work executes)
- “Task completed with identifier”
Missing any step = issue at that stage.
Resources
WWDC: 2019-707 (debugging commands), 2020-10063 (7 factors)
Skills: axiom-background-processing, axiom-background-processing-ref
Last Updated: 2025-12-31 Platforms: iOS 13+