Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Narrow bare rescue in Elixir so real errors like KeyError and typos propagate instead of being swallowed. Use to audit rescues and refactor error handling.
.claude/skills/oliver-kriska-narrow-bare-rescue/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 19% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 34% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 93% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 4% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 3% | 0% |
Turn rescue _ -> fallback into rescue _ in [ExceptionType1, ExceptionType2] -> fallback so programmer bugs propagate while known failure modes stay handled.
Bare rescues (rescue _ ->, rescue e -> — any form without an in clause) swallow every exception, including UndefinedFunctionError from typos, KeyError from misspelled map keys, and CompileError from bad HEEx templates. The symptom isn't a stack trace — it's a silent {:error, :generic} or a nil fallback. Bugs that should surface in tests or error reporters become quiet degradations.
The Erlang Secure Coding Guide makes the same case at the BEAM level — rule LNG-002 ("Do Not Use catch") warns that the legacy catch-all form conflates normal returns, throws, and errors. Bare rescue in Elixir is the direct analogue.
rescue _ -> or rescue e -> without an in clause. Every rescue mustlist exact exception types. The Credo check enforces this after cleanup lands.
exception is a behavioral regression — trace each call in the body before committing.
UndefinedFunctionError,CompileError, BadFunctionError, and BadArityError must propagate.
reraise e, __STACKTRACE__, never reraise e, []. Preserve the original stacktrace so Oban retry metadata and error reporters show the real origin.
mix compile --warnings-as-errors before committing. Typos in exception modulenames only surface at compile time — the code looks fine until it loads.
elixir# Before — masks programmer bugs def parse(body) do Jason.decode!(body) rescue _ -> %{} end # After — catches only what can actually fail here def parse(body) do Jason.decode!(body) rescue _ in [Jason.DecodeError, ArgumentError] -> %{} end
Applies identically to try … rescue … and to function-body def … rescue ….
The skill operates in three modes depending on scope:
/narrow-bare-rescue path/to/file.ex/narrow-bare-rescue lib/my_app/util//narrow-bare-rescue --allWhatever the scope, follow this sequence.
bashgrep -rn "^\s*rescue\s*$" <scope> | head -200
For each hit, read the 3 lines after to classify:
rescue _ -> or rescue var -> — bare, needs narrowingrescue _ in [...] -> or rescue var in Something -> — already typed, skiprescue ExceptionType -> (no variable binding) — already typed, skipRead the try / def body and trace what each call can raise. Don't guess from the function name — verify. Consult order:
references/taxonomy.md for the work type (JSON, Ecto, Money, HTTP, etc.).Most sites map cleanly to one row.
defexception when a specific library isn't in the taxonomy:bash grep -rn "defexception" deps/<libname>/lib/ | head -10
raise calls in the code path itself — if the body explicitly raisesRuntimeError, include it.
Priorities: cover everything the code can actually raise, exclude programmer-bug exceptions (see Iron Law #3), and prefer specific types (Jason.DecodeError beats ArgumentError if both could apply).
For files with ≥3 rescues sharing a taxonomy, hoist to a module attribute — see references/patterns.md for the module-attribute pattern, Oban reraise, ExCmd exit errors, and is_exception/1 replacements.
After changes in each file (or cluster of files), run:
bashmix compile --warnings-as-errors mix format <files_changed> mix test <test_files_for_affected_modules>
The compile step catches typos in exception module names — a real risk since you're writing module names from memory.
This skill narrows bare rescue clauses. It does not:
rescue e in [X] ->) — those are correctcatch clauses — throws and exits from the process are a separate concerntry/rescue with with or error-tuple plumbing — that's a larger refactorreferences/taxonomy.md — verified exception types per workcategory, plus library-specific gotchas (NimbleCSV, Plug, Phoenix LiveView tokenizer)
references/patterns.md — special patterns: is_exception/1,Oban reraise, ExCmd exit errors, module-attribute hoisting, partitioning large cleanups, the regression-prevention Credo check
— BEAM-level rationale for preferring narrow try ... catch / try ... rescue over the legacy catch-all form
Other measured skills in the registry, with their headline benchmark lift.