ruby/ruby · error · MarshalError

Specs #{name} from #{remote} is expected to be an Array but

Error message

Specs #{name} from #{remote} is expected to be an Array but was unexpected class #{specs.class}

What it means

Bundler::MarshalError raised in RubygemsIntegration#fetch_specs when the marshaled payload fetched from `#{remote.uri}/specs.#{Gem.marshal_version}.gz` unmarshals successfully but is not an Array. The check guards against gem sources that answer the standard specs endpoint with a well-formed Marshal object of the wrong shape. Note the surrounding rescue only tolerates FetchError for the optional prerelease_specs list; the class check itself is fatal.

Source

Thrown at lib/bundler/rubygems_integration.rb:365

      redefine_method(gem_class, :load_plugins) do |*|
        load_plugin_files specs.flat_map(&:plugins)
      end
    end

    def plain_specs
      Gem::Specification._all
    end

    def plain_specs=(specs)
      Gem::Specification.all = specs
    end

    def fetch_specs(remote, name, fetcher)
      require "rubygems/remote_fetcher"
      path = remote.uri.to_s + "#{name}.#{Gem.marshal_version}.gz"
      string = fetcher.fetch_path(path)
      specs = Bundler.safe_load_marshal(string)
      raise MarshalError, "Specs #{name} from #{remote} is expected to be an Array but was unexpected class #{specs.class}" unless specs.is_a?(Array)
      specs
    rescue Gem::RemoteFetcher::FetchError
      # it's okay for prerelease to fail
      raise unless name == "prerelease_specs"
    end

    def fetch_all_remote_specs(remote, gem_remote_fetcher)
      specs = fetch_specs(remote, "specs", gem_remote_fetcher)
      pres = fetch_specs(remote, "prerelease_specs", gem_remote_fetcher) || []

      specs.concat(pres)
    end

    def download_gem(spec, uri, cache_dir, fetcher)
      require "rubygems/remote_fetcher"
      uri = Bundler.settings.mirror_for(uri)
      redacted_uri = Gem::Uri.redact(uri)

View on GitHub (pinned to 0e5b888e1c)

Solutions

  1. Probe the endpoint directly: `curl -s <remote>/specs.4.8.gz | gunzip | head -c 32` — a healthy response starts with the Marshal array marker (\x04\b[)
  2. Remove or fix the bad mirror: unset stale BUNDLE_MIRROR__* variables and correct the Gemfile source URL
  3. Repair the private server (update Gemstash/Artifactory, purge its marshaled-specs cache) or fall back to rubygems.org for that host to confirm the diagnosis
Defensive patterns

Strategy: try-catch

Validate before calling

# probe the source's specs endpoint before pointing bundler at it
require "open-uri"
require "zlib"
raw = URI.open("#{remote}/specs.#{Gem.marshal_version}.gz").read
data = Bundler.safe_load_marshal(Zlib::Inflate.inflate(raw))
abort "#{remote} is not a spec-compliant gem server" unless data.is_a?(Array)

Type guard

def valid_remote_specs?(payload)
  payload.is_a?(Array) && payload.first.is_a?(Array)
end

Try / catch

begin
  Bundler.definition.resolve
rescue Bundler::MarshalError => e
  # e.message names the offending remote; drop the bad mirror/source and retry
  clean_mirror_config!
  retry
end

Prevention

When it happens

Trigger: A Gemfile source or mirror whose /specs.4.8.gz unmarshals to a Hash or other non-Array object; proxies/servers that re-marshal or rewrite the compact specs payload; calling Bundler.rubygems.fetch_specs(remote, name, fetcher) against a non-conforming host.

Common situations: BUNDLE_MIRROR__* env vars or source lines pointing at a broken private server (stale Gemstash, misconfigured Artifactory); a catch-all vhost answering the specs path; CDN edges serving cached, rewritten payloads.

Related errors


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