Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Reviews error-handling control flow for failures that get swallowed — an empty catch or bare pass, a broad catch that continues as if nothing happened, and log-and-swallow where the caller still needs to know it failed. For each, says whether to re-raise, catch a narrower type and handle it, or wrap with context. Use when someone asks you to review error handling, try/catch, or exception flow. Do NOT use for designing an error-type hierarchy, for general style review, or for reproducing a specific reported bug.
.claude/skills/silent-failure-review/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 484% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 445% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 489% | 0% |
| case-04 | ✗→✗ | = Same ✗ | 465% | 0% |
| case-01 | ✗→✗ | = Same ✗ | 483% | 0% |
Reviews error-handling control flow across Python, JavaScript/TypeScript, Go, Rust, and Java for one failure mode: an error is caught (or a failure value is returned) and then discarded, so the program keeps running as if it had succeeded. The code compiles and usually runs; the failure only shows up later as corrupt data, a stuck job, or a bug that is impossible to trace because the original error was thrown away.
The base model, handed a try that ends in an empty except or a catch that only logs, reads it as reasonable defensive code and moves on. It knows swallowing errors is bad but does not flag it by default, and rarely names which of the three remedies applies. This skill makes the check mandatory: identify what the swallow hides, then say precisely whether to re-raise, catch a narrower type and actually handle it, or wrap with context and re-raise.
This is about control flow that hides failures — not about designing an exception-class hierarchy, and not about working a single reported bug.
Activate when the user asks to:
except / catch / if err != nil block doing the right thing?Do NOT activate for designing an error/exception type hierarchy, for general readability or naming review, or for reproducing and root-causing one specific reported defect.
For each finding, name the pattern, say what failure it hides at runtime, and give the specific remedy (below). Do not stop at "this is bad practice."
The handler body is empty, a bare pass, or throws the value away without acting on it. Every failure in the guarded block vanishes with no trace.
except Exception: pass (or except: pass). Nothing is logged, nothing re-raised; the block silently produces a partial or wrong result.try { … } catch (e) {} with an empty body. A rejected await or thrown error is dropped and the code proceeds on undefined state.v, _ := doThing()) or an if err != nil {} with an empty body. The failure is thrown away and a zero-value v flows on.catch (Exception e) {} with nothing inside.A wide catch (Exception, Throwable, catch (e), catch (...)) wraps a block, and control simply falls through — often returning a default, None, or an empty list — as if the call had succeeded. This catches not just the expected failure but programming errors (a typo'd attribute, a KeyError, a null deref) that should have surfaced loudly.
except Exception: return []) hides an unexpected bug behind an empty result the caller treats as real data.Exception around several statements means an error in any of them is treated identically, so you can no longer tell a recoverable failure from a crash.except: also swallows KeyboardInterrupt and SystemExit, breaking Ctrl-C and shutdown.The handler logs the error and then continues or returns a success-looking value. The log is not the same as handling it: the caller has no way to know the operation failed, so it proceeds on incomplete state. This is the subtlest of the three because it looks responsible.
except Exception: logger.error(e); return None — the caller sees None and cannot distinguish "no result" from "it blew up".catch that logs and then falls through so the function returns its normal success value while the work never happened.Every swallow finding must end with one of these three, chosen deliberately:
raise after any cleanup; in Go return err; in Rust propagate with ? instead of .ok() / unwrap_or. Choose this when there is no sensible local recovery.except TimeoutError, catch (err) if err instanceof NotFound, a typed Go error check) and perform the real recovery — retry, fall back, return a documented default. Choose this when exactly one expected failure has a genuine local response and everything else should still propagate.raise ProcessingError(f"loading {id}") from e, JS throw new Error("...", { cause: e }), Rust .with_context(...) / .map_err(...), Go fmt.Errorf("...: %w", err). Choose this at a boundary where the raw error would be meaningless to the caller but the failure must still stop the flow.Swallowing is correct when the failure genuinely has no consequence and the intent is explicit. Do not raise a finding when:
.get/unwrap_or to supply a documented default, and the default is genuinely valid, not a mask for a bug.If the code already handles errors correctly, say so plainly instead of inventing a swallow.
Report each finding as: swallow pattern → what failure it hides at runtime → the remedy (re-raise / narrower catch + handle / wrap with context). Name the concrete mechanism for the remedy, not just "handle it better." If nothing is swallowed, say the error handling is sound rather than manufacturing a problem.
Other measured skills in the registry, with their headline benchmark lift.