hashicorp/terraform · critical
failed to encode main index: %s
Error message
failed to encode main index: %s
What it means
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.
Source
Thrown at internal/command/providers_mirror.go:319
continue
}
indexVersions[meta.Version.String()] = map[string]interface{}{}
if _, ok := indexArchives[version]; !ok {
indexArchives[version] = map[string]interface{}{}
}
indexArchives[version][platform.String()] = map[string]interface{}{
"url": archiveFilename, // a relative URL from the index file's URL
"hashes": []string{hash.String()}, // an array to allow for additional hash formats in future
}
}
mainIndex := map[string]interface{}{
"versions": indexVersions,
}
mainIndexJSON, err := json.MarshalIndent(mainIndex, "", " ")
if err != nil {
// Should never happen because the input here is entirely under
// our control.
panic(fmt.Sprintf("failed to encode main index: %s", err))
}
// TODO: Ideally we would do these updates as atomic swap operations by
// creating a new file and then renaming it over the old one, in case
// this directory is the docroot of a live mirror. An atomic swap
// requires platform-specific code though: os.Rename alone can't do it
// when running on Windows as of Go 1.13. We should revisit this once
// we're supporting network mirrors, to avoid having them briefly
// become corrupted during updates.
err = ioutil.WriteFile(filepath.Join(indexDir, "index.json"), mainIndexJSON, 0644)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to update indexes",
fmt.Sprintf("Failed to write an updated JSON index for %s: %s.", provider, err),
))
}
for version, archiveIndex := range indexArchives {
versionIndex := map[string]interface{}{View on GitHub (pinned to d32a084675)
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.
Example fix
// before
mainIndex := map[string]interface{}{
"versions": indexVersions, // indexVersions now holds a *Part somewhere
}
mainIndexJSON, err := json.MarshalIndent(mainIndex, "", " ")
if err != nil { panic(...) }
// after
// keep indexVersions contents to plain JSON-friendly types only
mainIndex := map[string]interface{}{
"versions": indexVersions,
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the index map is JSON-serializable before MarshalIndent.
func isJSONable(v interface{}) bool {
_, err := json.Marshal(v)
return err == nil
}
// before the panic site:
if !isJSONable(mainIndex) {
return fmt.Errorf("main index contains a non-serializable type")
} Type guard
// n/a — map[string]interface{}; guard via marshalling probe above Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to encode version index: %s
- backendDiags.Err()
- expected one provider requirement for the destination state
- expected exactly one provider requirement for the destinatio
- expected exactly one provider requirement for the destinatio
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/1a33cce25e3eb46d.
Report an issue: GitHub.