{"record":{"id":"1a33cce25e3eb46d","repo":"hashicorp/terraform","slug":"failed-to-encode-main-index-s","errorCode":null,"errorMessage":"failed to encode main index: %s","messagePattern":"failed to encode main index: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/command/providers_mirror.go","lineNumber":319,"sourceCode":"\t\t\t\tcontinue\n\t\t\t}\n\t\t\tindexVersions[meta.Version.String()] = map[string]interface{}{}\n\t\t\tif _, ok := indexArchives[version]; !ok {\n\t\t\t\tindexArchives[version] = map[string]interface{}{}\n\t\t\t}\n\t\t\tindexArchives[version][platform.String()] = map[string]interface{}{\n\t\t\t\t\"url\":    archiveFilename,         // a relative URL from the index file's URL\n\t\t\t\t\"hashes\": []string{hash.String()}, // an array to allow for additional hash formats in future\n\t\t\t}\n\t\t}\n\t\tmainIndex := map[string]interface{}{\n\t\t\t\"versions\": indexVersions,\n\t\t}\n\t\tmainIndexJSON, err := json.MarshalIndent(mainIndex, \"\", \"  \")\n\t\tif err != nil {\n\t\t\t// Should never happen because the input here is entirely under\n\t\t\t// our control.\n\t\t\tpanic(fmt.Sprintf(\"failed to encode main index: %s\", err))\n\t\t}\n\t\t// TODO: Ideally we would do these updates as atomic swap operations by\n\t\t// creating a new file and then renaming it over the old one, in case\n\t\t// this directory is the docroot of a live mirror. An atomic swap\n\t\t// requires platform-specific code though: os.Rename alone can't do it\n\t\t// when running on Windows as of Go 1.13. We should revisit this once\n\t\t// we're supporting network mirrors, to avoid having them briefly\n\t\t// become corrupted during updates.\n\t\terr = ioutil.WriteFile(filepath.Join(indexDir, \"index.json\"), mainIndexJSON, 0644)\n\t\tif err != nil {\n\t\t\tdiags = diags.Append(tfdiags.Sourceless(\n\t\t\t\ttfdiags.Error,\n\t\t\t\t\"Failed to update indexes\",\n\t\t\t\tfmt.Sprintf(\"Failed to write an updated JSON index for %s: %s.\", provider, err),\n\t\t\t))\n\t\t}\n\t\tfor version, archiveIndex := range indexArchives {\n\t\t\tversionIndex := map[string]interface{}{","sourceCodeStart":301,"sourceCodeEnd":337,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/command/providers_mirror.go#L301-L337","documentation":"This panic fires when json.MarshalIndent fails to encode the provider mirror's main 'index.json' structure in providers_mirror.go:319. The marshalled map (versions list) is built entirely from internal data, so the code comments 'Should never happen' — a failure means an unmarshallable Go type (func, chan, circular ref) was accidentally inserted into the map, i.e. a programmer regression.","triggerScenarios":"Invoked by `terraform providers mirror` while writing the top-level index.json. Triggers only if the indexArchives/indexVersions map gains a value type that encoding/json refuses (e.g. a func value, a channel, a recursive struct, or a type without marshal support). It cannot be triggered by user input, mirror target path, or provider versions.","commonSituations":"A maintainer edits providers_mirror.go and adds a non-serializable field (a *Part or io.Reader, an unexported type without JSON marshalling, a map with non-string keys) into indexVersions. CI runs the mirror command against any fixture and crashes here instead of producing a clean error.","solutions":["Inspect the most recent diff to providers_mirror.go for any value assigned into indexVersions or indexArchives that is not a JSON-serializable type (string, []string, map[string]interface{}, numbers, bools).","Run `go vet ./internal/command/` and add a unit test in providers_mirror_test.go that builds a minimal index and calls the marshal path to reproduce.","If you genuinely need a non-serializable type in the index, implement json.Marshaler on it or convert it to a plain map[string]interface{} before MarshalIndent."],"exampleFix":"// before\nmainIndex := map[string]interface{}{\n  \"versions\": indexVersions, // indexVersions now holds a *Part somewhere\n}\nmainIndexJSON, err := json.MarshalIndent(mainIndex, \"\", \"  \")\nif err != nil { panic(...) }\n// after\n// keep indexVersions contents to plain JSON-friendly types only\nmainIndex := map[string]interface{}{\n  \"versions\": indexVersions,\n}","handlingStrategy":"validation","validationCode":"// Validate the index map is JSON-serializable before MarshalIndent.\nfunc isJSONable(v interface{}) bool {\n  _, err := json.Marshal(v)\n  return err == nil\n}\n// before the panic site:\nif !isJSONable(mainIndex) {\n  return fmt.Errorf(\"main index contains a non-serializable type\")\n}","typeGuard":"// n/a — map[string]interface{}; guard via marshalling probe above","tryCatchPattern":null,"preventionTips":["Keep indexVersions/indexArchives values restricted to JSON-native types (string, []string, numbers, bool, nested maps).","Add a table-driven unit test that marshals a representative index and fails the build on any json.Marshal error.","Run `go vet` and revive/staticcheck on providers_mirror.go to catch func/chan fields."],"tags":["terraform-cli","json-marshal","panic","providers-mirror","invariant"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}