A sync job with `catch {}`. Errors silently disappear. Operators see no failures, but data is stale. Mesrai flagged the pattern.
The vulnerable diff
// services/sync.ts
async function syncAll(items: Item[]) {
for (const item of items) {
try {
await syncOne(item);
} catch {} // BUG: swallows everything
}
}What is wrong
Empty catch is the most aggressive form of error suppression. It hides validation errors, transient network failures, business-logic errors, and unrelated system errors equally. The original author likely meant 'ignore one bad item, keep processing' but the cost is invisibility — no logging, no metric, no alert. CWE-755 covers improper handling of exceptional conditions. The fix is to log every caught error and re-throw what the caller cannot recover from.
The attack
Symptom: 'why is the sync running but the data not updating?'
# Logs: 'sync complete' every 5 minutes.
# Reality: 80% of items errored out silently.
# Found via downstream data team comparing source vs destination.Months to detect, hours to fix.
Mesrai's review comment
mesraipilot · Bot · reviewed 30 sec ago
[mesrai] [code-review] [Logic] [Silent-Catch] [medium]
Empty catch hides every error. At minimum, log:
try { await syncOne(item); }
catch (err) {
log.error({ err, itemId: item.id }, "sync failed");
// optional: continue, throw, or track for retry
}
If you genuinely want to continue on failure, also emit a metric so
the rate is observable:
metrics.increment("sync.item.failed");
Never `catch {}` — even in a one-off script.The fix
// services/sync.ts — fixed
async function syncAll(items: Item[]) {
let failed = 0;
for (const item of items) {
try {
await syncOne(item);
} catch (err) {
log.error({ err, itemId: item.id }, "sync failed");
metrics.increment("sync.item.failed");
failed++;
}
}
if (failed > 0) log.warn({ failed, total: items.length }, "sync had failures");
}Every caught error is logged, counted, and the totals are surfaced at end. Operators see the failure rate. The job continues on individual failures (intentional) but nothing is invisible.
Why human review missed it
Silent catch is the easiest pattern to write and the hardest to debug later. Mesrai catches every empty catch block in non-test code.
Related rules + further reading
Mesrai rule pack: logic/no-silent-catch — flags empty catch blocks.
CWE-755 — Improper Handling of Exceptional Conditions.
ESLint: no-empty (with allowEmptyCatch: false).
Takeaway
Catch must log. Or re-throw. Or both. Never empty. Mesrai catches every silent catch.