hashicorp/packer · error
malformed index.json: %w
Error message
malformed index.json: %w
What it means
The remote Getter caches plugin versions in an index.json whose expected shape is {"versions": {...}}. parseIndex unmarshals the fetched bytes into that structure; if the body is not valid JSON (or `versions` isn't an object), it wraps the unmarshal error as "malformed index.json". Called by loadIndex whenever the cached index is missing or stale.
Source
Thrown at packer/plugin-getter/remote/getter.go:79
// entryHasProtocolVersion reports whether a checksum-file entry name carries
// an x-prefixed protocol version field.
var entryHasProtocolVersion = regexp.MustCompile(`_x\d+\.\d+_`)
// pluginIndex is the validated form of a plugin's index.json. Versions are
// keyed by their canonical core string (e.g. "1.1.4"); all other index
// content is ignored.
type pluginIndex struct {
pluginType string
versions map[string]struct{}
}
// parseIndex validates raw index.json content into a pluginIndex.
func parseIndex(data []byte, pluginType string) (*pluginIndex, error) {
var raw struct {
Versions map[string]struct{} `json:"versions"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return nil, fmt.Errorf("malformed index.json: %w", err)
}
if len(raw.Versions) == 0 {
return nil, fmt.Errorf("index.json contains no versions")
}
idx := &pluginIndex{
pluginType: pluginType,
versions: map[string]struct{}{},
}
for k := range raw.Versions {
ver, err := goversion.NewVersion(k)
if err != nil {
log.Printf("[WARN] remote-getter: ignoring unparseable version %q in index.json", k)
continue
}
idx.versions[ver.String()] = struct{}{}
}
if len(idx.versions) == 0 {View on GitHub (pinned to eb36e3c3e4)
Solutions
- Inspect the URL/body returned for the index (curl it) to see what non-JSON content is being served.
- Delete the corrupted cached index.json in the plugin directory and re-run the install so it is re-fetched.
- Fix proxy/network equipment that is injecting HTML instead of passing through the JSON response.
Example fix
// before (index.json on disk)
<html>Moved Temporarily</html>
// after
{
"versions": {
"1.1.1": {}
}
} Defensive patterns
Strategy: try-catch
Validate before calling
body, err := os.ReadFile(indexPath)
if err == nil && !json.Valid(body) {
os.Remove(indexPath) // corrupt cache; force re-fetch
} Try / catch
_, err := g.Get("releases", opts)
if err != nil && strings.Contains(err.Error(), "malformed index.json") {
os.Remove(cachedIndexJSON)
_, err = g.Get("releases", opts) // re-fetch clean index
} Prevention
- Never hand-edit index.json in the plugin directory.
- Ensure proxies don't inject HTML into JSON responses.
- Validate JSON after interrupted downloads before trusting a cache.
When it happens
Trigger: loadIndex fetches index.json and parseIndex receives bytes that fail json.Unmarshal — e.g. an HTML error page, an empty body, or truncated download served at the index URL.
Common situations: A corporate proxy or captive portal returning an HTML login page instead of JSON; a partially written/corrupted cached index.json on disk; a custom PACKER_PLUGIN_CONFIG_PATH layout with a hand-edited index.json; CDN error responses with status < 400.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- %q not implemented
- malformed filename expected %s{version}_{os}_{arch}
- wrong version: %s does not match expected %s
- wrong system, expected %s_%s got %s_%s
- index.json contains no versions
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/c9b4131c78023dcc.
Report an issue: GitHub.