ruby/ruby · error · ArgumentError

Attempting to visit unknown element #{e.inspect}

Error message

Attempting to visit unknown element #{e.inspect}

What it means

The SafeMarshal base visitor dispatches each parsed element through a table (DISPATCH) built from every constant under Gem::SafeMarshal::Elements; the table's default routes any unrecognized element class to visit_unknown_element, which raises ArgumentError. Reaching it means the element object being visited is not one of the known element types — normally an internal version skew between the reader that produced the elements and the visitor consuming them, or a foreign/monkey-patched element class.

Source

Thrown at lib/rubygems/safe_marshal/visitors/visitor.rb:21

module Gem::SafeMarshal::Visitors
  class Visitor
    def visit(target)
      send DISPATCH.fetch(target.class), target
    end

    private

    DISPATCH = Gem::SafeMarshal::Elements.constants.each_with_object({}) do |c, h|
      next if c == :Element

      klass = Gem::SafeMarshal::Elements.const_get(c)
      h[klass] = :"visit_#{klass.name.gsub("::", "_")}"
      h.default = :visit_unknown_element
    end.compare_by_identity.freeze
    private_constant :DISPATCH

    def visit_unknown_element(e)
      raise ArgumentError, "Attempting to visit unknown element #{e.inspect}"
    end

    def visit_Gem_SafeMarshal_Elements_Array(target)
      target.elements.each {|e| visit(e) }
    end

    def visit_Gem_SafeMarshal_Elements_Bignum(target); end
    def visit_Gem_SafeMarshal_Elements_False(target); end
    def visit_Gem_SafeMarshal_Elements_Float(target); end

    def visit_Gem_SafeMarshal_Elements_Hash(target)
      target.pairs.each do |k, v|
        visit(k)
        visit(v)
      end
    end

    def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(target)

View on GitHub (pinned to 0e5b888e1c)

Solutions

  1. Align versions: update rubygems and bundler together (`gem update --system`, `bundle update --bundler`) so the reader and visitor come from the same release
  2. Remove or update any gem/plugin that monkey-patches Gem::SafeMarshal internals
  3. If you call the visitor directly, only pass elements produced by Gem::SafeMarshal::Reader from the same rubygems version
  4. Rescue ArgumentError around the safe_load call and treat the payload as unparseable (refetch or reject)

Example fix

# before
def render(elements)
  visitor.visit(elements)
end

# after
begin
  visitor.visit(elements)
rescue ArgumentError => e
  raise if e.message !~ /unknown element/
  # reader/visitor mismatch: regenerate elements with the current reader
end
Defensive patterns

Strategy: try-catch

Type guard

known_element = ->(e) { Gem::SafeMarshal::Elements.constants.any? { |c| Gem::SafeMarshal::Elements.const_get(c) === e } }

Try / catch

begin
  visitor.visit(element)
rescue ArgumentError => e
  raise unless e.message.include?('unknown element')
  # reader/visitor version skew: regenerate or reject payload
end

Prevention

When it happens

Trigger: Feeding the visitor element objects not defined in Gem::SafeMarshal::Elements: mixing a newer reader (which emits new element types) with an older visitor from another rubygems/bundler version, calling Gem::SafeMarshal::Visitors::ToRuby#visit directly on a hand-built element tree, or a gem monkey-patching the reader to introduce custom element classes.

Common situations: Vendored or pinned old rubygems/Bundler running next to newer installed components; internal API misuse by a tool that constructs element objects itself; rare corrupted streams that decode into unexpected structure.

Related errors


AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21). Data as JSON: /api/errors/9d8bf396f4e70202. Report an issue: GitHub.