Here's a bug that's easy to miss and harder to debug: your AI runs perfectly on one input path, silently does nothing on another, and there's no error — just missing results.
I hit this recently in an on-device inspection app. The flow is straightforward: capture a photo (camera or photo library), run AI hazard detection, overlay bounding boxes, trigger violation alerts if needed.
Camera captures worked great. Gallery picks saved the photo fine. But no bounding boxes appeared, no alerts fired. The AI was just... not running.
What Actually Happened
Here's the stripped-down structure:
// Camera capture handler
const handleRequestCapture = async (uri: string) => {
await savePhoto(uri);
await detectAndSave(uri); // ✅ AI runs
checkViolationAlerts();
};
// Library pick handler
const handleLibraryPick = async (uri: string) => {
await savePhoto(uri);
// ❌ detectAndSave was never called
};
The library path was added later, modelled on the save logic but not the full inference pipeline. No crash. No warning. Just missing detections.
The fix was six lines — copy the detectAndSave + alert block into the library handler. But finding it took longer than writing the fix.
Why This Pattern Is Easy To Ship
On-device AI inference tends to get wired in during the happy path. You build the camera flow first, the AI gets integrated there, everything works in demos. Then you add the gallery pick as a "nice to have" — and because you're only thinking about the file I/O, you copy the save logic but not the inference call.
There's no type error. No lint warning. The function name (handleLibraryPick) doesn't imply inference should happen. The photo saves successfully, so from the app's perspective, nothing broke.
Lessons From The Fix
1. Treat every input path as a first-class citizen.
If AI inference is core to your feature, it should run regardless of how the image arrived. Camera, gallery, deep link, background upload — all of them.
2. Extract inference into a shared pipeline.
After the fix I refactored toward a single processPhoto(uri) function that both handlers call. Now if the pipeline changes, it changes in one place.
const processPhoto = async (uri: string) => {
await savePhoto(uri);
await detectAndSave(uri);
checkViolationAlerts();
};
const handleRequestCapture = (uri: string) => processPhoto(uri);
const handleLibraryPick = (uri: string) => processPhoto(uri);
3. End-to-end tests that cover input variants.
Unit tests on detectAndSave wouldn't have caught this — the function worked fine, it just wasn't being called. What you need is an integration test that exercises each entry point and asserts that inference results exist.
4. Visibility into what ran.
This is the sneaky part: when AI silently doesn't run, you need observability to know it happened. Adding a log line (AI inference: skipped | ran on <uri>) to your inference call sites makes this class of bug immediately obvious in development.
The Broader Pattern
This isn't unique to AI. Any side-effect that only gets wired to one code path — analytics events, permission checks, cache invalidation — can silently miss inputs added later. But with AI inference it's particularly painful because the failure mode is invisible and the debugging surface is narrow (you're looking at model output, not a thrown error).
Build the pipeline once, call it everywhere.
Top comments (0)