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
- 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[)
- Remove or fix the bad mirror: unset stale BUNDLE_MIRROR__* variables and correct the Gemfile source URL
- 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
- Validate private gem servers with the probe above once, in a smoke test, not on every install
- Keep BUNDLE_MIRROR__* config in a reviewed file (bundle config) rather than scattered env vars
- Upgrade Gemstash/Artifactory in lockstep with RubyGems marshal-version changes
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
- 17
- RubyGems doesn't know about trust policy '#{trust_policy}'.
- can't find executable #{exec_name} for gem #{gem_name}
- 17
- Unexpected class %s present in marshaled data. Only %s are a
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/c5262ea38f81bad8.
Report an issue: GitHub.