Skip to content

Add a setting to let an exception raised in a rescue_from block propagate - #3040

Open
dduugg wants to merge 1 commit into
ruby-grape:masterfrom
dduugg:raise-rescue-handler-errors
Open

dduugg wants to merge 1 commit into
ruby-grape:masterfrom
dduugg:raise-rescue-handler-errors

Conversation

@dduugg

@dduugg dduugg commented Oct 22, 2026 •

Copy link
Copy Markdown

Since #2703 (3.3.0), an exception raised inside a rescue_from block is redispatched, so it never reaches Rack middleware mounted above Grape, and there is no opt-out. In #3003 that middleware is Active Record's DatabaseSelector with a custom resolver that retries a request on the primary after an ActiveRecord::ReadOnlyError. The app re-raised the error from rescue_from :all for it, and on 3.3 and later the request answers 500 where it used to recover.

What

This adds Grape.config.raise_rescue_handler_errors, off by default, as @dblock sketched in #3003. With it on, the rescue StandardError in run_rescue_handler re-raises the exception untouched and skips redispatch. A block that calls error! still renders, because the Halt it raises is rescued first.

Grape.configure do |config|
  config.raise_rescue_handler_errors = true
end

It is the handler-side counterpart of raise_rendering_errors (#2840) and is wired the same way: a setting beside it in lib/grape.rb, a bullet in the README's Configuration list, and the opt-out documented in the README section it opts out of.

With the setting on, a raising handler behaves as it did on 3.2.1. The table shows what a route that raises gets when rescue_from :all does each of these:

handler 3.2.1 master setting on
raise e raises 500 raises
raises Wrapped, which has its own rescue_from raises Wrapped 422 from the Wrapped handler raises Wrapped
raises a Grape::Exceptions::Base raises rendered with its status raises
with: :method that raises raises 500 raises
calls error!(..., 422) 422 422 422

Why a global setting

#3003 also discussed rescue_from ActiveRecord::ReadOnlyError, propagate: true. Grape picks a handler from e.class, so propagate: would miss a ReadOnlyError that arrives as another exception's cause, which the resolver in #3003 also retries (comment). This PR leaves propagate: out. It can follow separately if you want both.

Notes

  • No UPGRADING entry: the setting is off by default, so it is additive.
  • With it on, rescue_from :internal_grape_exceptions never sees a handler's exception, and neither rack.exception nor grape.exception is set. The README says so.
  • Grape's own handlers (error_response, default_rescue_handler) run through run_rescue_handler too, so with the setting on an unexpected exception from one of them also propagates, as on 3.2.1. A rendering failure is still answered by the failsafe unless raise_rendering_errors is on.

Tests

Three examples in spec/grape/middleware/error_spec.rb, under "when a rescue_from block raises":

Without the lib/ change the first two fail. Moving the setting check ahead of the Halt rescue fails the third.

Fixes #3003.

…gate

Since ruby-grape#2703, Grape redispatches an exception raised inside a rescue_from
block, so it no longer reaches Rack middleware mounted above Grape. That
fixed ruby-grape#2482, and it also stopped a block from handing an exception to
such middleware by re-raising it. Active Record's DatabaseSelector with
a custom resolver that retries a request on the primary after an
ActiveRecord::ReadOnlyError is one: an API that re-raised the error from
rescue_from :all for it now answers 500 where the request used to
recover.

Grape.config.raise_rescue_handler_errors brings back the earlier
behaviour, as raise_rendering_errors (ruby-grape#2840) does for rendering
failures. With it on, whatever a rescue_from block raises is re-raised
untouched. A block that calls error! still renders, because Halt is
rescued ahead of it. Off by default.

Fixes ruby-grape#3003.
@dduugg
dduugg force-pushed the raise-rescue-handler-errors branch from 17ea474 to 7ce8f36 Compare October 22, 2026 00:57
@dblock
dblock requested a review from ericproulx October 23, 2026 16:25
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@dblock

dblock commented Oct 23, 2026

Copy link
Copy Markdown
Member

I fixed danger-changelog in 0.10.1, dblock/danger-changelog#85

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.

Let a rescue_from handler propagate an exception instead of rendering it

2 participants