{"record":{"id":"1c32c9033ff7e93d","repo":"ruby-grape/grape","slug":"grape-rescue-from-klass-will-never-run-cov","errorCode":null,"errorMessage":"Grape: rescue_from #{klass} will never run — #{covered_by} was registered earlier in the same scope and is matched first. Register the more specific class before the broader one.","messagePattern":"Grape: rescue_from #(.+?) will never run — #(.+?) was registered earlier in the same scope and is matched first\\. Register the more specific class before the broader one\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/grape/util/shadowed_rescue_handlers.rb","lineNumber":44,"sourceCode":"      # an outer +rescue_from ArgumentError+ is the documented behaviour rather\n      # than a mistake. Classes sharing a handler object are skipped too, since\n      # +rescue_from A, B+ registers one handler for both and the entry that\n      # loses to the other changes nothing.\n      def warn_about(registered, mapping)\n        return if registered.empty?\n\n        mapping.each do |klass, handler|\n          covered_by, = registered.find { |already, existing| klass <= already && !existing.equal?(handler) }\n          next unless covered_by\n\n          warn(message_for(klass, covered_by))\n        end\n      end\n\n      def message_for(klass, covered_by)\n        return \"Grape: rescue_from #{klass} was already registered in this scope; the first handler is kept and this one will never run.\" if klass == covered_by\n\n        \"Grape: rescue_from #{klass} will never run — #{covered_by} was registered earlier in the same scope \" \\\n          'and is matched first. Register the more specific class before the broader one.'\n      end\n    end\n  end\nend\n","sourceCodeStart":26,"sourceCodeEnd":50,"githubUrl":"https://github.com/ruby-grape/grape/blob/22d7975629846a3c0c7bd2b34e140a7a1b4af8f6/lib/grape/util/shadowed_rescue_handlers.rb#L26-L50","documentation":"Grape emits this warning (via Kernel#warn to $stderr, not a raised exception) when a rescue_from registration in a scope can never execute because a broader exception class registered EARLIER in the same scope is matched first. Grape's error middleware resolves handlers with find over the ordered handler map, so the first registered class that covers the raised exception (klass <= already, i.e. the raised class is a subclass of the registered one) wins — making the later, more specific handler dead code. The fix the message prescribes is ordering: register the most specific class before the broader one. It only compares a scope's own registrations: an inner scope's rescue_from legitimately overrides an outer one, and rescue_from A, B (one shared handler object) is skipped.","triggerScenarios":"Calling rescue_from with exception classes in a Grape::API class or namespace such that a later call names a class that is a descendant of a class already registered in the SAME scope — e.g. `rescue_from StandardError` followed by `rescue_from ArgumentError`, or `rescue_from Grape::Exceptions::Base` followed by `rescue_from Grape::Exceptions::ValidationErrors`. Only fires for class-keyed registrations made with the default rescue_subclasses: true (the subclasses: true map passed to add_rescue_handlers); the meta selectors rescue_from :all / :grape_exceptions / :internal_grape_exceptions never trigger it, and re-registering the exact same class with the same handler object is also skipped. Fires at class-body load time, the moment rescue_from is called.","commonSituations":"Copying an endpoint's error handling into an API class that already declares a catch-all rescue_from StandardError near the top. Refactors or gem upgrades that insert a broad handler above existing specific ones. Assuming Ruby's native rescue semantics (most specific clause wins regardless of order) instead of Grape's first-match-wins registration order. Moving a broad handler from a base class into the same scope where specific handlers already live. Previously (older Grape) this was silent dead code — a version adding this diagnostic surfaces latent ordering bugs.","solutions":["Reorder the rescue_from calls in that scope so more specific exception classes are registered before broader ones (e.g. ArgumentError before StandardError), matching how you would order native Ruby rescue clauses.","If the broad handler is meant as a catch-all, replace `rescue_from StandardError` with the meta selector `rescue_from :all` — it is consulted only after all class-keyed handlers, so specific handlers keep winning regardless of order.","Move the broad handler to an outer scope (base API class) and keep specific handlers in the inner namespace/endpoint — cross-scope shadowing is documented, intended behavior (nearest scope wins) and is not warned.","Delete the shadowed registration if it is redundant dead code and no reorder is intended."],"exampleFix":"# before\nclass MyAPI < Grape::API\n  rescue_from StandardError { |_e| error!('server error', 500) }\n  rescue_from ArgumentError { |_e| error!('bad input', 400) } # never runs: StandardError matched first\nend\n\n# after (option 1: specific first)\nclass MyAPI < Grape::API\n  rescue_from ArgumentError { |_e| error!('bad input', 400) }\n  rescue_from StandardError { |_e| error!('server error', 500) }\nend\n\n# after (option 2: keep order, make the catch-all a meta selector)\nclass MyAPI < Grape::API\n  rescue_from ArgumentError { |_e| error!('bad input', 400) }\n  rescue_from :all with: ->(_e) { error!('server error', 500) } # consulted after class-keyed handlers\nend","handlingStrategy":"validation","validationCode":"# Predicate mirroring Grape's check (klass <= already means the earlier broader class covers klass):\n# Returns the already-registered broader class that would shadow klass, or nil.\ndef covering_class(registered_classes, klass)\n  registered_classes.find { |broader| klass <= broader } # klass <= broader <=> klass.ancestors.include?(broader)\nend\n\n# Use it when wiring handlers programmatically so a shadow never gets registered:\nhandlers = [[ArgumentError, ->(e) { error!(e.message, 400) }],\n            [StandardError, ->(e) { error!('server error', 500) }]]\nregistered = []\nhandlers.each do |klass, handler|\n  broader = covering_class(registered, klass)\n  raise ArgumentError, \"#{klass} would be shadowed by earlier #{broader}\" if broader\n  registered << klass\n  # rescue_from klass, with: handler ...\nend","typeGuard":"# Narrowing predicate for Ruby: true if registering klass in this scope is safe\n# (i.e. no earlier broader class already covers it).\ndef rescueable_here?(registered_classes, klass)\n  registered_classes.none? { |broader| klass <= broader }\nend\n\nrescueable_here?([StandardError], ArgumentError) # => false — would be shadowed, skip or reorder\nrescueable_here?([ArgumentError], StandardError) # => true — broader after specific is fine","tryCatchPattern":"# It is a Kernel#warn to $stderr, not a raised exception — 'catch' it by asserting on stderr\n# in the spec that loads the API class, so a shadowed handler fails CI instead of shipping:\nRSpec.describe MyAPI do\n  it 'registers no shadowed rescue_from handlers' do\n    expect { load 'app/api/my_api.rb' }\n      .not_to output(/Grape: rescue_from .* will never run/).to_stderr\n  end\nend","preventionTips":["Treat rescue_from order like native Ruby rescue order: most specific class first, broadest last, within each scope.","Use rescue_from :all for catch-alls instead of rescue_from StandardError — class-keyed handlers always beat the meta selector, so order stops mattering.","Keep broad handlers on the base/outer API class and specific handlers in namespaces/endpoints; inner scopes intentionally override outer ones.","Add a spec asserting the API class loads without the 'Grape: rescue_from ... will never run' warning on $stderr.","Remember re-registering the same class in a scope keeps the FIRST handler (and its position) — don't rely on a later registration to override; scope it more tightly instead."],"tags":["grape","rescue-from","ruby","error-handling","dead-code","registration-order","stderr-warning"],"backgroundTag":"exception-handler-shadowing","analyzedSha":"22d7975629846a3c0c7bd2b34e140a7a1b4af8f6","analyzedAt":"2026-08-21T17:03:54.627Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}