{"record":{"id":"662fc1dd1648151a","repo":"ruby-grape/grape","slug":"returning-or-throwing-a-hash-from-a-rescue-handler","errorCode":null,"errorMessage":"Returning or throwing a Hash from a rescue handler is deprecated. Use `error!(...)` or a `Grape::Exceptions::ErrorResponse` instead.","messagePattern":"Returning or throwing a Hash from a rescue handler is deprecated\\. Use `error!\\(\\.\\.\\.\\)` or a `Grape::Exceptions::ErrorResponse` instead\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/grape/middleware/error.rb","lineNumber":220,"sourceCode":"      end\n\n      def error!(message, status = default_status, headers = {}, backtrace = [], original_exception = nil)\n        env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route\n        merged_headers = { Rack::CONTENT_TYPE => content_type }.merge!(headers)\n        error = Grape::Exceptions::ErrorResponse.new(\n          status:, message:, headers: merged_headers, backtrace:, original_exception:\n        )\n        rack_response(status, merged_headers, format_message(error))\n      end\n\n      def error?(response)\n        case response\n        when Grape::Exceptions::ErrorResponse\n          true\n        when Hash\n          return false unless response.key?(:message) && response.key?(:status) && response.key?(:headers)\n\n          Grape.deprecator.warn(\n            'Returning or throwing a Hash from a rescue handler is deprecated. ' \\\n            'Use `error!(...)` or a `Grape::Exceptions::ErrorResponse` instead.'\n          )\n          true\n        else\n          false\n        end\n      end\n    end\n  end\nend\n","sourceCodeStart":202,"sourceCodeEnd":232,"githubUrl":"https://github.com/ruby-grape/grape/blob/22d7975629846a3c0c7bd2b34e140a7a1b4af8f6/lib/grape/middleware/error.rb#L202-L232","documentation":"A `rescue_from` handler used to be allowed to return (or throw) a Hash of `{ message:, status:, headers: }`, which Grape converted into a rack response. That contract is deprecated: `error?` detects exactly that three-key Hash, warns via `Grape.deprecator`, and directs you to `error!` or a `Grape::Exceptions::ErrorResponse`. Note that a Hash missing any of the three keys is not treated as an error response at all — it becomes the response body verbatim.","triggerScenarios":"`rescue_from NotFoundError do |e| { message: 'missing', status: 404, headers: {} } end`; throwing the Hash: `throw :error, { message: ..., status: ..., headers: ... }` from a handler or custom middleware.","commonSituations":"Apps written against very old Grape; upgrading across this deprecation; handlers shared with Sinatra-style code that return hashes.","solutions":["Call `error!('missing', 404)` inside the handler and let Grape build the response","Or construct the structured object: `throw :error, Grape::Exceptions::ErrorResponse.new(message: 'missing', status: 404, headers: {})`","Audit handlers during upgrade with `Grape.deprecator.behavior = :raise`"],"exampleFix":"# before\nrescue_from NotFoundError do |e|\n  { message: 'not found', status: 404, headers: {} }\nend\n\n# after\nrescue_from NotFoundError do |e|\n  error!('not found', 404)\nend","handlingStrategy":"validation","validationCode":"# CI guard: hash-returning rescue handlers raise in the test env\nGrape.deprecator.behavior = :raise if Rails.env.test?","typeGuard":null,"tryCatchPattern":"# In request specs, assert on the rendered error instead of the handler's return value:\n# expect { get '/missing' }.to change { response.status }.to(404)\n# — this fails loudly if a handler still returns a Hash","preventionTips":["Make every rescue_from handler end in `error!` or a thrown `Grape::Exceptions::ErrorResponse`","Request-spec each rescued endpoint so a Hash-shaped return value never passes silently","Do not port Sinatra hash-return error blocks; rewrite them with `error!` during the Grape migration"],"tags":["grape","rescue-from","deprecation","error-handling","middleware"],"backgroundTag":"deprecated-api-usage","analyzedSha":"22d7975629846a3c0c7bd2b34e140a7a1b4af8f6","analyzedAt":"2026-08-21T17:03:54.627Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}