plugins/elixir-phoenix/skills/narrow-bare-rescue/SKILL.md
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.
npx skillsauth add oliver-kriska/claude-elixir-phoenix narrow-bare-rescueInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 must
list exact exception types. The Credo check enforces this after cleanup lands.UndefinedFunctionError,
CompileError, BadFunctionError, and BadArityError must propagate.reraise e, __STACKTRACE__, never reraise e, []. Preserve the original stack
trace so Oban retry metadata and error reporters show the real origin.mix compile --warnings-as-errors before committing. Typos in exception module
names only surface at compile time — the code looks fine until it loads.# 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.
grep -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:
Check references/taxonomy.md for the work type (JSON, Ecto, Money, HTTP, etc.).
Most sites map cleanly to one row.
Grep deps for defexception when a specific library isn't in the taxonomy:
grep -rn "defexception" deps/<libname>/lib/ | head -10
Check raise calls in the code path itself — if the body explicitly raises
RuntimeError, 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:
mix 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 refactor${CLAUDE_SKILL_DIR}/references/taxonomy.md — verified exception types per work
category, plus library-specific gotchas (NimbleCSV, Plug, Phoenix LiveView tokenizer)${CLAUDE_SKILL_DIR}/references/patterns.md — special patterns: is_exception/1,
Oban reraise, ExCmd exit errors, module-attribute hoisting, partitioning large
cleanups, the regression-prevention Credo checkcatch
— BEAM-level rationale for preferring narrow try ... catch / try ... rescue over
the legacy catch-all formtools
Scope or freeze which files Claude can edit during debugging, a refactor, or review. Use when edits should stay in specific dirs, or for a read-only investigate lock. Backed by a sentinel + PreToolUse hook.
development
Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.
development
Reduce mix output noise (5-15% token savings) by installing rtk filters that compress mix test/credo/dialyzer/compile output before it reaches Claude. Use when long mix output floods context.
tools
Elixir testing patterns — ExUnit, Mox, factories, LiveView test helpers. Use when working on *_test.exs, test/support/, factory files, or fixing test failures.