{"record":{"id":"df1b6a3fcf08142e","repo":"hashicorp/packer","slug":"s-w","errorCode":null,"errorMessage":"%s: %w","messagePattern":"%s: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packer/plugin-getter/remote/getter.go","lineNumber":168,"sourceCode":"\tdir = path.Join(append(append([]string{}, parts[1:len(parts)-1]...), pluginType)...)\n\treturn dir, pluginType\n}\n\n// loadIndex fetches and validates the plugin's index.json, reusing the\n// parsed result across calls for the same plugin.\nfunc (g *Getter) loadIndex(opts plugingetter.GetOptions) (*pluginIndex, string, error) {\n\tdir, pluginType := pluginPath(opts)\n\tif g.index != nil && g.index.pluginType == pluginType {\n\t\treturn g.index, dir, nil\n\t}\n\turl := g.BaseURL + \"/\" + dir + \"/index.json\"\n\tdata, err := g.fetch(url)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tidx, err := parseIndex(data, pluginType)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"%s: %w\", url, err)\n\t}\n\tg.index = idx\n\treturn idx, dir, nil\n}\n\n// versionURL resolves the version directory URL for the version being\n// fetched, failing when the index does not list it.\nfunc (g *Getter) versionURL(opts plugingetter.GetOptions) (*pluginIndex, string, error) {\n\tidx, dir, err := g.loadIndex(opts)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tversion := opts.VersionString()\n\tif _, ok := idx.versions[version]; !ok {\n\t\treturn nil, \"\", fmt.Errorf(\n\t\t\t\"version %s of %s is not listed in %s/%s/index.json\",\n\t\t\tversion, idx.pluginType, g.BaseURL, dir)\n\t}","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/hashicorp/packer/blob/eb36e3c3e48a036f3e8cc94087636ee72e1303c9/packer/plugin-getter/remote/getter.go#L150-L186","documentation":"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.","triggerScenarios":"Get or versionURL triggers loadIndex; the fetched index.json at `url` is invalid JSON, empty of versions, or contains no usable semver versions.","commonSituations":"Corrupted cached index.json on disk; proxy returning HTML for the index URL; custom registry serving an empty or non-semver index.","solutions":["Open the URL printed in the error and inspect what the index endpoint actually returns.","Delete the cached index.json and re-run the install to force a clean re-fetch.","Fix the root cause per the wrapped inner error (malformed JSON / no versions / no usable versions)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"data, err := fetch(indexURL)\nif err == nil {\n    var probe struct{ Versions map[string]json.RawMessage `json:\"versions\"` }\n    if json.Unmarshal(data, &probe) != nil || len(probe.Versions) == 0 {\n        os.Remove(cachePath) // preempt the wrapped loadIndex failure\n    }\n}","typeGuard":null,"tryCatchPattern":"if _, err := g.Get(\"releases\", opts); err != nil {\n    if strings.Contains(err.Error(), \"malformed index.json\") ||\n        strings.Contains(err.Error(), \"no usable versions\") {\n        // errors.Is/errors.As on the wrapped inner error; purge cache and refetch\n    }\n}","preventionTips":["Treat the URL prefix in the message as the pointer to the bad index endpoint.","Unwrap with errors.Is/errors.As to distinguish JSON vs version-set root causes.","Purge cached index.json after network interruptions or proxy changes."],"tags":["packer","plugin-getter","error-wrapping","index-parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"eb36e3c3e48a036f3e8cc94087636ee72e1303c9","analyzedAt":"2026-09-05T13:20:43.127Z","contentChangedAt":"2026-09-05T13:20:43.127Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}