Original Reddit post

If I’d shipped on day 15, every user would have hit this crash starting day 31. The people who kept my app the longest would be the first to get burned. I was checking icon sizes in my Settings views. That’s it. The most boring possible task. I launched the macOS build to eyeball some toggles. Spinning beach ball. Fatal crash. Turns out the app had been archiving deleted items for 30+ days. On this launch, the cleanup manager decided it was finally time to permanently delete them. The cascade delete hit photo data stored in iCloud that hadn’t been downloaded to the Mac. SwiftData tried to snapshot objects that didn’t exist locally. Uncatchable fatal error. App dead. The comment in the code said “after 30 days, it’s very likely the data is available.” That comment was the bug. Why I never caught this in testing The trigger isn’t a code path. It’s data age plus environment state. No test data is 30 days old Simulators have perfect local data, no iCloud sync delays Unit tests use in-memory stores CI runs on fresh environments every time My dev machine has been on good Wi-Fi the whole time To catch this, you’d need to create items, archive them, set your device clock forward 30 days, disconnect from iCloud, and relaunch. I’ve never done that. You probably haven’t either. 5 time bomb patterns probably hiding in your codebase After fixing the crash, I searched my whole project for the same class of bug. Here’s what turned up:

  1. Deferred deletion with cascade relationships. The one that got me. “Archive now, delete later” with a day threshold. The parent object deletes fine, but child objects with cloud-synced storage may have unresolved faults after sitting idle for weeks. Fatal crash, no recovery.
  2. Cache expiry with model relationships. Same trigger, different clock. Cache entries (OCR results, AI responses) set to expire after 30/60/90 days. If those cache objects have relationships to other persisted models, the expiry purge can hit the same fault crash.
  3. Trial and subscription expiry paths. What happens when the free trial ends? Not what the paywall looks like. Does the AI assistant crash because the session was initialized with trial permissions that no longer exist? Does the “subscribe” button actually work, or was StoreKit never initialized because the feature was always available during development?
  4. Background task accumulation. Thumbnail generation, sync reconciliation, cleanup jobs that work fine processing 5 items a day. After 3 weeks of the app sitting in the background, they wake up and try to process 500 items at once. Memory limits, stale references, timeout kills.
  5. Date-threshold state transitions. Objects that change state based on date math (warranties expiring, loans overdue). The transition code assumes the object is fully loaded. After months, relationships may have been pruned by cloud sync, or the item may have been deleted on another device while this one was offline. How to find them Grep your codebase for date arithmetic near destructive operations: byAdding.*day near delete|purge|cleanup|expire cacheExpiry|expiresAt|ttl|maxAge daysRemaining|trialEnd|canUse BGTaskScheduler|scheduleCleanup For every hit, ask one question: “If this runs for the first time 90 days after the data was created, with bad network, what breaks?” What I took away from this Most testing asks “does this work?” Time bomb testing asks “does this still work after someone trusts your app for a month?” I added this as a formal audit wave to my open source audit skill set, radar-suite (Claude Code skills for auditing Swift/SwiftUI apps). But the grep patterns work in any language with lazy loading, cloud sync, or deferred operations. Which is basically everything. submitted by /u/BullfrogRoyal7422

Originally posted by u/BullfrogRoyal7422 on r/ClaudeCode