Never let a failure look like an empty result
Make 'nothing found' and 'the call failed' different values, and never collapse one into the other.
`except: pass` and `catch {}` are how a broken system reports that everything is fine. The two states are genuinely different: an empty result is an answer you should publish, and a failure is a question you should retry or surface. Code that returns the same value for both removes your ability to tell which happened — permanently, because the information is gone by then.
What goes wrong: A search that returns nothing because the backend is down looks identical to a search that legitimately matched nothing, and ships to users as an empty page.
You are violating it when
- A bare `except:`, `except Exception: pass`, or an empty catch block.
- A function returns `[]` on both success-with-no-rows and error.
- An `or None` / `|| []` fallback wrapping a call that can fail.
The usual objection: That defensive defaults make software robust. A default that hides a failure makes software confidently wrong, which is strictly worse than software that stops.
An empty result is a successful answer with zero matches. A failed call is not an answer at all. Code needs to preserve that distinction in its return type, status, or error path so later code can decide whether to show “no results,” retry, degrade, alert, or stop.
This works because the information is still available at the boundary where the operation fails. Once an exception is swallowed and replaced with an empty list, null, false, or a 200 response, every caller downstream receives a lie with no evidence left to recover the truth. Observability has the same problem: a masked failure often looks healthy from outside because the system returned something normal-shaped.
The misconception is that defensive defaults make software robust. Defaults are useful when they represent a real business fallback, but dangerous when they erase uncertainty. A system that fails fast on a broken dependency, bad configuration, or unexpected exception is easier to test and repair than one that confidently publishes fabricated emptiness.
When an agent is writing code, this distinction is especially important because agents often generate broad catch blocks and “safe” fallback values to keep tests or demos moving. Reviews and tests need to reject those shortcuts unless the error is recorded and the caller receives an explicit failure value it is forced to handle.
Install it
npx klay practices add no-silent-fallbacks.klay/practices/no-silent-fallbacks.mdcreate# No silent fallbacks `except: pass` is how a broken system reports that everything is fine. "Nothing found" and "the call failed" are genuinely different states. An empty result is an answer you should publish. A failure is a question you should retry, surface, or fail on. Code that returns the same value for both destroys your ability to tell which happened — permanently, because by the time anyone asks, the information is gone. ## The shape of the bug ```python def search(q):
The previews are the first lines of each file; the command writes them in full. Existing files are never overwritten.
How you know it stuck
npx klay practices audit reports this check for this practice:
broad-except-swallow
Where this comes from
- Error handlingDefines API errors as indeterminate, separate from both success and absence of data.
- Google SRE: Load Balancing with Client Side ThrottlingAdds the reliability stance that overload degradation must be explicit, not normal-looking.
- REL05-BP04 Fail fast and limit queues - Reliability PillarExplains why fast, visible failure is safer than hiding defects behind defaults.
- 503 Service Unavailable - HTTP | MDNShows the web protocol already models service failure separately from successful empty content.
Questions
- Sometimes I really do want to ignore an error.
- Then ignore it out loud: catch the specific exception you mean, log it, and write the comment saying why this one is safe. The rule is against the bare and unexplained version.