Product & Mission6 min read

Why We Made Our AI Tools Fail Loudly Instead of Returning Your Original Image

A tool that quietly returns your input when the model fails looks like it worked — and wastes your time worse than an error would. The "no silent fallback" rule, and the three real bugs it caught in our own code.

The worst failure mode in an AI tool isn't an error message. It's a tool that looks like it worked: you click "make sticker," it spins, and hands back your original photo with rounded corners. You can't tell the model failed — you just think the tool is bad. We adopted a hard rule to kill this class of bug: no silent fallback. A failed model path must surface a typed, visible error — never the input image, never a stub, never a classical no-op dressed up as success.

Auditing our own code against that rule caught three real offenders.

1. The sticker that wasn't a cutout

The sticker generator took an image, added padding and rounded corners, and returned it. It assumed the input was already a cut-out subject. Drop in a normal photo and you got the whole photo with rounded corners on a white background — "sticker complete!" The fix wires a real background-removal cutout before the framing step, so a sticker is an actual die-cut subject. If the cutout fails, it throws — it doesn't hand back the framed original.

2. Source separation that returned the input twice

Our audio source-separation tool was supposed to split a track into stems. The implementation returned the input audio as both the "vocals" stem and the "other" stem — i.e. nothing was separated, but the result shape looked successful. That's the rule violated exactly: a no-op presented as success. Until the real Demucs model is wired, it now surfaces an honest "not available yet" error instead of pretending. An error you can act on beats a fake result you can't trust.

3. [object Object] in the answer

Ask-Image returned "answer": "[object Object]". The model's result was an object, and somewhere it got String()-ed instead of having its .answer/.text field extracted. We added one shared text-coercer that pulls the real string out of whatever shape the model returns — string, {answer}, [{generated_text}], nested — and never yields [object Object]. Every text tool now routes through it.

The guardrail that makes it systematic

Catching these one by one isn't enough; you want the architecture to make silent success hard. So we added a guard layer between the model call and the post-processor:

  • Result-shape assertionsassertDims, assertMaskData — turn a degenerate model output into one loud, typed error naming the tool, instead of a Cannot read properties of undefined (reading 'width') deep in a canvas call.
  • Session integrity — a model session with no input names (the signature of a truncated download) throws at load time, not three tools later.
  • EP telemetry — we log which execution provider actually bound, so "it silently ran on the slow path" is observable instead of suspected.

Why this matters to you

A loud failure respects your time: you know to retry, switch modes, or use a different image. A silent fallback wastes it: you ship the "sticker," notice later it's just your photo, and blame yourself. We'd rather show you an honest error once than a fake success you discover at the worst moment.