Routing 24 silent catches to a real error bus
The "blocked on solo dev constraint" note in my own bug log was wrong. The work wasn't blocked, I just hadn't done it. Enumerating the silent catches in the iOS app for a side project turned up 24 of them across three views — exactly the kind of code you can ship for years and never see fail.
What was happening
Pattern looked like this in 24 places:
} catch {
print("Failed to load medications: \(error)")
}
print goes nowhere on a real user's device. The app had an
existing ErrorReporter shipped a while back that POSTs to an
errors/log endpoint with screen, action, device model, OS, app
version, free memory, and network type, with 60-second dedupe.
Almost none of the catch blocks were wired to it.
The breakdown:
CheckInView.swift: 7MedicationsView.swift: 6SOSView.swift: 11
The SOS view is the worst place to have silent failures.
What I found
Two of the 24 catches already had real fallback logic — they reset local state when an API call failed. The other 22 just printed and moved on. A user hitting "Resolve SOS" while their network was flaky would see no progress, the call would fail, the print would go nowhere, and they'd be left looking at a screen that didn't do what they tapped.
The fix
A one-line addition to each catch, keeping any existing fallback logic intact:
} catch {
print("Failed to resolve SOS: \(error)")
error.report(
type: .api,
context: ErrorContext(screen: "SOSView",
action: "Failed to resolve SOS")
)
}
The .report is the existing extension on Error from
ErrorReporter.swift. The two catches that had fallback code got
the error.report inserted right after the print, before the
state-reset, so nothing else changed.
Built clean in the simulator:
xcodebuild build \
-project "Check on Mine.xcodeproj" \
-scheme "Check on Mine" \
-destination "generic/platform=iOS Simulator" \
-configuration Debug \
CODE_SIGNING_ALLOWED=NO
# BUILD SUCCEEDED
I had originally written a one-shot Python script that did the regex-replace across all 24 sites. The script is throwaway; the pattern is what matters.
What I'd do differently
There are roughly 80 more catches in the same codebase across Watch, AppDelegate, HealthKit, LocationTracker, AuthService, TripView, etc. The same pattern applies to all of them. The honest answer to "why didn't I do all 80" is that 24 was the biggest thing I could finish in one focused sitting without breaking something else.
The bigger lesson is to never start a project by writing
print("Failed to X: \(error)"). If you're going to log to
nowhere on every failure, write error.report(...) from day one
and route it to a no-op in dev. Future-you will save a day of
grep + sed.