The Swallowed Exception That Broke Production: AI Slop Pattern Deep Dive
Pattern 1 of 14. An empty catch block swallowed a 401 in a production dashboard. The dashboard showed blank data for a full sprint before someone traced it to a single line an AI agent wrote. Here is how the pattern works, why AI generates it, and how to catch it every time.
This is the first in a 14-part series breaking down every AI slop pattern we have documented in production code. Each post covers what the pattern looks like, why AI generates it, what breaks in production, and how aislop catches it deterministically.
The incident
A production dashboard went blank. Not crashed — blank. Data tables rendered as empty states. Charts showed no series. For three weeks, the team assumed the upstream data pipeline had an issue. They traced through the ETL, the database, the API layer — all clean.
The root cause was a single try-catch block an AI agent had written three months earlier:
try {
const user = await api.getUser(id);
displayDashboard(user.data);
} catch (e) {
// Empty — error silently swallowed
}The API call returned a 401. The catch block did nothing. The function returned undefined. Three components downstream crashed silently. The dashboard rendered blank. Nobody noticed because no error was thrown — the catch consumed everything.
This is Pattern 1 in our taxonomy: The Optimistic Catch Block. It appears in approximately 80% of AI-generated PRs touching an API or database — found in 41 of 50 PRs in one audit.
What it looks like in code
// What AI writes:
try {
await saveToDatabase(data);
} catch (e) {
console.error("Something went wrong");
// No rethrow. No retry. No meaningful recovery.
}
// Or worse:
try {
await saveToDatabase(data);
} catch (e) {
// Empty — nothing at all
}Why AI generates this
AI models are trained on code that compiles — not code that survives. The model sees try-catch as a syntactic pattern to complete, not a recovery mechanism to reason about. It has no concept of which errors are recoverable, which should propagate, or what context a future debugger needs.
The training distribution heavily weights "catch → log → continue" patterns from tutorial code. Stack Overflow snippets. Blog examples. Production-hardened error handling — the kind that comes from debugging 2 AM incidents — is dramatically underrepresented in training data.
What breaks in production
Silent failures cascade. A 401 is swallowed. A timeout is swallowed. A database connection failure is swallowed. Each one produces undefined behavior in the caller, which produces undefined behavior in the caller's caller, and so on up the stack. The developer spends hours tracing the root cause because no error was ever logged.
The documented production incidents linked to this pattern include connection pool exhaustion (litellm PR #21213), Redis pool failures (#21717), and an event loop leak (langchain PR #35352) — all caused by swallowed exceptions in error handling code AI agents generated.
The fix
// GOOD — log with context AND rethrow
catch (error) {
logger.error("Failed to save data", {
error: error.message,
requestId: req.headers['x-request-id'],
orderId: data?.orderId,
});
throw error;
}
// BETTER — recover meaningfully
catch (error) {
if (error.code === 'CARD_DECLINED') {
return { error: 'Your card was declined.' };
}
throw error; // Everything else propagates
}How aislop catches this
aislop has a deterministic rule for swallowed exceptions. It scans every try-catch block in every file and checks three things:
1. Is the catch block empty? → Fires pro/error-handling/empty-catch
2. Does it only contain a comment or console.log? → Fires pro/error-handling/log-only-catch
3. Is there a missing finally for resource cleanup? → Fires pro/connection-leaks/database-pool
Deterministic means deterministic. A try-catch with no rethrow always produces a diagnostic. Every time. No LLM guesswork. No "the model was having a bad day." Same code, same result, same block.
The key rule
The community has converged on one principle: log or throw, not both. The layer that handles the exception logs it. The layer that cannot handle it throws it. Never do both in the same catch block. Every catch block should either recover meaningfully or let the error propagate. Silence is not a strategy.
Check your repos. Search for empty catch blocks. Count them. If you find more than zero, you have AI slop in production — and it is only a matter of time before one of them swallows the error you needed to see.