Warn when a rescue_from handler can never run - #2842
Open
ericproulx wants to merge 1 commit into
Open
Conversation
ericproulx
force-pushed
the
fix/warn-shadowed-rescue-from
branch
from
August 1, 2026 10:01
a4cead1 to
3953456
Compare
Danger ReportNo issues found. |
Grape::Middleware::Error resolves a registered handler with #find, so within a
scope the first matching class wins. A handler registered for a class an
earlier one already covers is therefore dead code, and silently so:
rescue_from StandardError do ... end # wins
rescue_from ArgumentError do ... end # never runs
Swapping the two lines is all it takes, but nothing said so -- and the
neighbouring `rescue_from :all` behaves the other way round, since it is
consulted only after the registered handlers, so a specific class registered
after it still wins. Two documented ways of saying "catch everything",
ordering differently against a later specific handler.
Warn at definition time rather than reorder: which handler should win is the
author's call, and :all already covers "broad first, specific still wins".
The check lives in Grape::Util::ShadowedRescueHandlers, called from
#add_rescue_handlers where the scope's own registrations are known.
Compared within a scope only -- across scopes the nearest registration
deliberately wins, so an inner rescue_from StandardError shadowing an outer
rescue_from ArgumentError is the documented behaviour, not a mistake. Classes
sharing a handler object are skipped too, since `rescue_from A, B` registers
one handler for both and the entry that loses changes nothing. Exact-match
handlers (rescue_subclasses: false) are consulted before the subclass-matching
ones and never match a descendant, so they cannot shadow each other either.
README gains the ordering rule, which was only implied.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
fix/warn-shadowed-rescue-from
branch
from
August 1, 2026 11:07
3953456 to
8f6202a
Compare
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Grape::Middleware::Errorresolves a registered handler with#find, so within a scope the first matching class wins. A handler registered for a class an earlier one already covers is therefore dead code, and silently so:Swapping the two lines is all it takes, but nothing said so — and the neighbouring
rescue_from :allbehaves the other way round, since it lives inall_rescue_handlerand is consulted only after the registered handlers:ArgumentErrorrescue_from :allthenrescue_from ArgumentErrorArgumentErrorhandler ✅rescue_from StandardErrorthenrescue_from ArgumentErrorStandardErrorhandlerTwo documented ways of saying "catch everything", ordering differently against a later specific handler — while the README describes
rescue_from :allas rescuing "allStandardErrorexceptions".Approach
Warn at definition time rather than reorder. Which handler should win is the author's call, and
:allalready offers "broad first, specific still wins" to anyone who wants it. No behaviour changes.Deliberately quiet in the cases that are not mistakes:
rescue_from A, Bsharing one handlerwith:on bothrescue_subclasses: falsepairsrescue_from :allthen a specific classAcross scopes the nearest registration deliberately wins, so an inner
rescue_from StandardErrorshadowing an outerrescue_from ArgumentErroris documented behaviour, not a mistake. Exact-match handlers are consulted before the subclass-matching ones and never match a descendant, so they cannot shadow each other either.The check lives in
Grape::Util::ShadowedRescueHandlers, called from#add_rescue_handlerswhere a scope's own registrations are known — kept out ofInheritableSettingitself, which is at itsMetrics/ClassLengthlimit and is a settings store rather than a place for DSL diagnostics.README gains the ordering rule, which was only implied.
Test plan
inheritable_setting_spec.rbcovering both warning messages and the five quiet cases; verified the two warning ones fail without thelib/change.🤖 Generated with Claude Code