defensive.error-obscuring
April 27, 2026 ยท View on GitHub
Flags catch blocks that replace the original failure with a default value or generic error.
- Family:
defensive - Severity:
strong - Scope:
file - Requires:
file.tryCatchSummaries
How it works
The rule reports small try/catch blocks when the catch clause does one of these things:
- returns a default literal
- throws a generic replacement error
- logs and then returns a default
Those patterns make downstream diagnosis harder because the original failure is flattened or hidden.
Flagged examples
export function readConfig(raw: string) {
try {
return JSON.parse(raw);
} catch {
return {};
}
}
export function loadProfile(id: string) {
try {
return fetchProfile(id);
} catch {
throw new Error("failed to load profile");
}
}
Usually ignored
export function readConfig(raw: string) {
try {
return JSON.parse(raw);
} catch (error) {
logger.error({ error });
throw error;
}
}
How to fix / do this better
Prefer preserving failure meaning instead of replacing it with a cheap fallback.
Better patterns:
- rethrow the original error
- wrap with context while preserving
cause - return a deliberate result type that makes the failure explicit instead of pretending the operation succeeded
export function loadProfile(id: string) {
try {
return fetchProfile(id);
} catch (error) {
throw new Error(`Failed to load profile ${id}`, { cause: error });
}
}
If you truly need a fallback value, keep it narrow, document why it is safe, and avoid erasing the original failure in code paths that still need diagnosis.
Scoring
Each flagged catch uses the shared try/catch scoring helper, then the file total is capped at 8.
Generic rethrows are still noisy, but scored slightly lower than silent default-return patterns.
Benchmark signal
Small pinned rule benchmark (manifest):
- Signal rank: #8 of 9
- Signal score: 0.66 / 1.00
- Best separating metric: findings / file (0.83)
- Hit rate: 5/6 AI repos vs 5/5 mature OSS repos
- Full results: rule signal report