Summary
Sentry::Rails::ControllerTransaction#sentry_around_action calls request.params from an ensure block. When the request body cannot be parsed, that call raises ActionDispatch::Http::Parameters::ParseError after the action has already produced its response, and because the exception is raised from ensure it replaces that response with a 400 Bad Request.
https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/lib/sentry/rails/controller_transaction.rb#L45-L50
ensure
child_span.set_http_status(response.status)
...
# all params request + body
params = request.params
The callback is installed with prepend_around_action, so it wraps every action in the application. An action that deliberately does not touch params — reading request.raw_post instead, which is the usual shape for a webhook endpoint that verifies an HMAC over the raw body — is therefore no longer able to answer a request whose body is malformed. Merely having the SDK initialized changes the response.
ActionDispatch::Request#POST does not memoize the failure, so every call raises again; nothing later in the stack can recover.
Why it is easy to miss
ActionDispatch::Http::Parameters::ParseError is in IGNORE_DEFAULT, so the SDK does not report the exception it just caused. In production the only visible symptom is "this endpoint returns 400 instead of 200", with nothing in Sentry.
It is also invisible in most test suites: without a DSN the SDK is effectively inert, so the instrumentation never runs and the action's real response is observed.
Impact for us
A webhook endpoint that must answer 200 (the provider cancels the installation otherwise) verifies the signature over request.raw_post and tolerates a non-JSON body by design:
def payload
@payload ||= JSON.parse(request.raw_post, symbolize_names: true)
rescue JSON::ParserError
{}
end
With the SDK initialized this endpoint answers 400.
Reproduction
Rails 8.1.3.1, sentry-rails 7.0.0, Ruby 4.0.7. Any controller action that does not read params:
class BoomController < ActionController::Base
def quiet = head(:ok)
end
POST /quiet
Content-Type: application/json
not json
- SDK not initialized:
200 OK
- SDK initialized:
400 Bad Request (no event reported)
Suggestion
Nothing in that ensure block should be able to raise. Reading the params defensively would be enough, e.g.
params = begin
request.params
rescue ActionDispatch::Http::Parameters::ParseError, ActionController::BadRequest
nil
end
request.query_parameters, read a few lines above, can raise ActionController::BadRequest for a malformed query string for the same reason.
Workaround
For anyone hitting this before it is fixed: an around_action registered inside Sentry's (a plain around_action is appended, so it always nests inside the prepend_around_action one) can settle the parameters before Sentry's ensure runs:
around_action do |_controller, action|
action.call
ensure
begin
request.params
rescue ActionDispatch::Http::Parameters::ParseError
request.request_parameters = {}
end
end
Actions that read params themselves still get the usual 400.
Summary
Sentry::Rails::ControllerTransaction#sentry_around_actioncallsrequest.paramsfrom anensureblock. When the request body cannot be parsed, that call raisesActionDispatch::Http::Parameters::ParseErrorafter the action has already produced its response, and because the exception is raised fromensureit replaces that response with a400 Bad Request.https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/lib/sentry/rails/controller_transaction.rb#L45-L50
The callback is installed with
prepend_around_action, so it wraps every action in the application. An action that deliberately does not touchparams— readingrequest.raw_postinstead, which is the usual shape for a webhook endpoint that verifies an HMAC over the raw body — is therefore no longer able to answer a request whose body is malformed. Merely having the SDK initialized changes the response.ActionDispatch::Request#POSTdoes not memoize the failure, so every call raises again; nothing later in the stack can recover.Why it is easy to miss
ActionDispatch::Http::Parameters::ParseErroris inIGNORE_DEFAULT, so the SDK does not report the exception it just caused. In production the only visible symptom is "this endpoint returns 400 instead of 200", with nothing in Sentry.It is also invisible in most test suites: without a DSN the SDK is effectively inert, so the instrumentation never runs and the action's real response is observed.
Impact for us
A webhook endpoint that must answer
200(the provider cancels the installation otherwise) verifies the signature overrequest.raw_postand tolerates a non-JSON body by design:With the SDK initialized this endpoint answers
400.Reproduction
Rails 8.1.3.1, sentry-rails 7.0.0, Ruby 4.0.7. Any controller action that does not read
params:200 OK400 Bad Request(no event reported)Suggestion
Nothing in that
ensureblock should be able to raise. Reading the params defensively would be enough, e.g.request.query_parameters, read a few lines above, can raiseActionController::BadRequestfor a malformed query string for the same reason.Workaround
For anyone hitting this before it is fixed: an
around_actionregistered inside Sentry's (a plainaround_actionis appended, so it always nests inside theprepend_around_actionone) can settle the parameters before Sentry'sensureruns:Actions that read
paramsthemselves still get the usual400.