Skip to content

Rank a nested rescue_from :all ahead of the enclosing scope's handlers - #3073

Merged
ericproulx merged 1 commit into
masterfrom
fix/nested-rescue-all-precedence
Nov 10, 2026
Merged

ericproulx merged 1 commit into
masterfrom
fix/nested-rescue-all-precedence

Conversation

@ericproulx

Copy link
Copy Markdown
Contributor

A rescue_from :all declared in a namespace or a mounted API lost to any class handler of an enclosing scope that matched:

rescue_from ZeroDivisionError do
  error!('root', 500)
end

namespace :alpacas do
  rescue_from :all do
    error!('alpacas', 503)
  end
  get { 1 / 0 } # answered 'root'
end

A nested class handler already wins over an outer one whatever the classes are (#1405, README "Rescuing exceptions inside namespaces"). So an inner rescue_from StandardError beat the outer ZeroDivisionError, while an inner rescue_from :all, the same catch-all written differently, lost to it. The cause was ordering: Middleware::Error scanned the class handlers of every scope before it consulted :all, which is a single inherited value with no position in the chain.

Supersedes #1982. See #1975.

Change

  • InheritableSetting#rescue_handlers ends the handlers of a scope that declared rescue_from :all with an :all entry. The entry is added on read, so it always follows that scope's own class handlers, however the declarations were interleaved. The catch-all therefore ranks behind its own scope's class handlers and ahead of every enclosing scope's.
  • Middleware::Error stops the scan at that entry for an exception :all catches (a StandardError) and goes on to the catch-all as before, after the Grape exception checks.
    • An exception :all does not catch, such as NotImplementedError, scans past the entry and still reaches an enclosing rescue_from Exception.
    • An exception raised inside a rescue_from block is never handed to a catch-all, so redispatch ignores the entry and an enclosing scope's handler still takes it.
  • The exact-match handlers (rescue_subclasses: false) are untouched. They are scanned before every subclass handler, whatever scope registered them, which predates this change and applies to nested class handlers too.

Behaviour change

The mount-order specs from #2384 (spec/grape/api/mount_and_helpers_order_spec.rb) pinned the outer class handler winning over a mounted API's :all. They now expect the mounted API's catch-all, and still give the same answer for either mount order, which is what they were written to check. UPGRADING describes the change and how to keep the old answer: register the class in the nested scope too.

Specs

spec/grape/api_spec.rb, next to the existing "rescues defined by mounted" precedence specs:

  • a nested :all beats an outer class handler (fails on master);
  • the nested scope's own class handlers still beat its :all;
  • what :all does not catch reaches the outer rescue_from Exception, while a StandardError stops at the nested :all;
  • an exception raised by the nested :all block is redispatched to the outer class handler.

Each of the last three guard specs was mutation-checked: putting the :all entry ahead of the scope's own handlers, letting redispatch stop at it, or letting it stop non-StandardErrors each fails exactly the matching spec.

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the fix/nested-rescue-all-precedence branch from bcf2f67 to a3a9633 Compare November 10, 2026 09:04
A rescue_from :all declared in a namespace or a mounted API lost to any
class handler of an enclosing scope that matched, although a nested class
handler wins over an outer one whatever the classes are (#1405). The class
handlers of every scope were scanned before the catch-all was consulted.

The scope that declared rescue_from :all now ends its handlers with an
:all entry, so the catch-all ranks behind its own scope's class handlers
and ahead of every enclosing scope's. An exception it does not catch, one
that is not a StandardError, scans past it as before, and so does one
raised inside a rescue_from block, which is never handed to a catch-all.

The mount-order specs from #2384 pinned the outer class handler winning;
they now expect the mounted API's catch-all, for either mount order.

Supersedes #1982. See #1975.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the fix/nested-rescue-all-precedence branch from a3a9633 to 9045960 Compare November 10, 2026 10:52
@ericproulx
ericproulx merged commit 0964ce5 into master Nov 10, 2026
50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants