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

  1. 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).
  2. 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.
  3. 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

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


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/1a33cce25e3eb46d. Report an issue: GitHub.