ruby-grape/grape · warning

Grape: rescue_from #{klass} will never run — #{covered_by} w

Error message

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.

What it means

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.

Source

Thrown at lib/grape/util/shadowed_rescue_handlers.rb:44

      # an outer +rescue_from ArgumentError+ is the documented behaviour rather
      # than a mistake. Classes sharing a handler object are skipped too, since
      # +rescue_from A, B+ registers one handler for both and the entry that
      # loses to the other changes nothing.
      def warn_about(registered, mapping)
        return if registered.empty?

        mapping.each do |klass, handler|
          covered_by, = registered.find { |already, existing| klass <= already && !existing.equal?(handler) }
          next unless covered_by

          warn(message_for(klass, covered_by))
        end
      end

      def message_for(klass, covered_by)
        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

        "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.'
      end
    end
  end
end

View on GitHub (pinned to 22d7975629)

Solutions

  1. 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.
  2. 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.
  3. 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.
  4. Delete the shadowed registration if it is redundant dead code and no reorder is intended.

Example fix

# before
class MyAPI < Grape::API
  rescue_from StandardError { |_e| error!('server error', 500) }
  rescue_from ArgumentError { |_e| error!('bad input', 400) } # never runs: StandardError matched first
end

# after (option 1: specific first)
class MyAPI < Grape::API
  rescue_from ArgumentError { |_e| error!('bad input', 400) }
  rescue_from StandardError { |_e| error!('server error', 500) }
end

# after (option 2: keep order, make the catch-all a meta selector)
class MyAPI < Grape::API
  rescue_from ArgumentError { |_e| error!('bad input', 400) }
  rescue_from :all with: ->(_e) { error!('server error', 500) } # consulted after class-keyed handlers
end
Defensive patterns

Strategy: validation

Validate before calling

# Predicate mirroring Grape's check (klass <= already means the earlier broader class covers klass):
# Returns the already-registered broader class that would shadow klass, or nil.
def covering_class(registered_classes, klass)
  registered_classes.find { |broader| klass <= broader } # klass <= broader <=> klass.ancestors.include?(broader)
end

# Use it when wiring handlers programmatically so a shadow never gets registered:
handlers = [[ArgumentError, ->(e) { error!(e.message, 400) }],
            [StandardError, ->(e) { error!('server error', 500) }]]
registered = []
handlers.each do |klass, handler|
  broader = covering_class(registered, klass)
  raise ArgumentError, "#{klass} would be shadowed by earlier #{broader}" if broader
  registered << klass
  # rescue_from klass, with: handler ...
end

Type guard

# Narrowing predicate for Ruby: true if registering klass in this scope is safe
# (i.e. no earlier broader class already covers it).
def rescueable_here?(registered_classes, klass)
  registered_classes.none? { |broader| klass <= broader }
end

rescueable_here?([StandardError], ArgumentError) # => false — would be shadowed, skip or reorder
rescueable_here?([ArgumentError], StandardError) # => true — broader after specific is fine

Try / catch

# It is a Kernel#warn to $stderr, not a raised exception — 'catch' it by asserting on stderr
# in the spec that loads the API class, so a shadowed handler fails CI instead of shipping:
RSpec.describe MyAPI do
  it 'registers no shadowed rescue_from handlers' do
    expect { load 'app/api/my_api.rb' }
      .not_to output(/Grape: rescue_from .* will never run/).to_stderr
  end
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ruby-grape/grape@22d7975629 (2026-08-21). Data as JSON: /api/errors/1c32c9033ff7e93d. Report an issue: GitHub.