hashicorp/packer · error

%s: %w

Error message

%s: %w

What it means

loadIndex fetches index.json for a plugin type and passes the bytes to parseIndex; on parse failure it wraps the parse error with the URL prefix ("%s: %w") so callers (versionURL, Get) can see which remote index was bad. It is a contextual wrapper — the root cause is error 334, 335, or 336.

Source

Thrown at packer/plugin-getter/remote/getter.go:168

	dir = path.Join(append(append([]string{}, parts[1:len(parts)-1]...), pluginType)...)
	return dir, pluginType
}

// loadIndex fetches and validates the plugin's index.json, reusing the
// parsed result across calls for the same plugin.
func (g *Getter) loadIndex(opts plugingetter.GetOptions) (*pluginIndex, string, error) {
	dir, pluginType := pluginPath(opts)
	if g.index != nil && g.index.pluginType == pluginType {
		return g.index, dir, nil
	}
	url := g.BaseURL + "/" + dir + "/index.json"
	data, err := g.fetch(url)
	if err != nil {
		return nil, "", err
	}
	idx, err := parseIndex(data, pluginType)
	if err != nil {
		return nil, "", fmt.Errorf("%s: %w", url, err)
	}
	g.index = idx
	return idx, dir, nil
}

// versionURL resolves the version directory URL for the version being
// fetched, failing when the index does not list it.
func (g *Getter) versionURL(opts plugingetter.GetOptions) (*pluginIndex, string, error) {
	idx, dir, err := g.loadIndex(opts)
	if err != nil {
		return nil, "", err
	}
	version := opts.VersionString()
	if _, ok := idx.versions[version]; !ok {
		return nil, "", fmt.Errorf(
			"version %s of %s is not listed in %s/%s/index.json",
			version, idx.pluginType, g.BaseURL, dir)
	}

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. Open the URL printed in the error and inspect what the index endpoint actually returns.
  2. Delete the cached index.json and re-run the install to force a clean re-fetch.
  3. Fix the root cause per the wrapped inner error (malformed JSON / no versions / no usable versions).
Defensive patterns

Strategy: try-catch

Validate before calling

data, err := fetch(indexURL)
if err == nil {
    var probe struct{ Versions map[string]json.RawMessage `json:"versions"` }
    if json.Unmarshal(data, &probe) != nil || len(probe.Versions) == 0 {
        os.Remove(cachePath) // preempt the wrapped loadIndex failure
    }
}

Try / catch

if _, err := g.Get("releases", opts); err != nil {
    if strings.Contains(err.Error(), "malformed index.json") ||
        strings.Contains(err.Error(), "no usable versions") {
        // errors.Is/errors.As on the wrapped inner error; purge cache and refetch
    }
}

Prevention

When it happens

Trigger: Get or versionURL triggers loadIndex; the fetched index.json at `url` is invalid JSON, empty of versions, or contains no usable semver versions.

Common situations: Corrupted cached index.json on disk; proxy returning HTML for the index URL; custom registry serving an empty or non-semver index.

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.

Related errors


AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05). Data as JSON: /api/errors/df1b6a3fcf08142e. Report an issue: GitHub.