gohugoio/hugo · error

failed to decode module download result

Error message

failed to decode module download result: %w

What it means

Raised after `go mod download -json` reportedly succeeded (exit 0) but json.NewDecoder could not unmarshal its stdout into the goModule struct. The data Go wrote is not the shape Hugo expects, so the result is unusable. This is almost always a toolchain/output-format skew, not a user config problem.

Solutions

  1. Confirm the Go binary matches what this Hugo build supports: `go version` and compare against the Hugo release notes for your version.
  2. Run `go mod download -json path@version` manually and inspect whether stdout is clean JSON (no stray log lines).
  3. Clear caches: `go clean -modcache && hugo mod clean` then rebuild to rule out cache corruption.
  4. If a Go wrapper (asdf/gimme) injects output, invoke Hugo with a direct Go binary on PATH.

Example fix

// verify clean JSON output
go mod download -json github.com/x/y@v1.2.3 | head
// if extra log lines appear, fix the Go wrapper/shell rc, then:
hugo mod clean && hugo
Defensive patterns

Strategy: validation

Validate before calling

// Confirm go emits clean, decodable JSON for this module
import ("encoding/json"; "os/exec")

func validateDownloadJSON(path, version string) error {
  out, err := exec.Command("go", "mod", "download", "-json", path+"@"+version).Output()
  if err != nil { return err }
  var m struct{ Dir, Path, Version string }
  return json.Unmarshal(out, &m)
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: downloadModuleVersion receives non-empty stdout that fails struct decode — e.g. a newer Go emitting additional/renamed fields that break strict decoding, stdout contaminated by a wrapper (gimme, asdf, proxy) prepending logs, or a partial/truncated buffer due to a killed process.

Common situations: Go version far ahead/behind the Hugo release (field mismatches), shell rc files printing output when `go` is invoked, containerised Hugo where stdout is teed to a logger, or a corrupted module cache producing garbled cached JSON.

Understand the failure class

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/eaa85c9ef575f6df. Report an issue: GitHub.

Appendix: source

Thrown at modules/client.go:473

	// No valid cache, need to query.
	args := []string{"mod", "download", "-json", fmt.Sprintf("%s@%s", path, version)}

	b := &bytes.Buffer{}
	err := c.runGo(context.Background(), b, args...)
	if err != nil {
		// The -json flag makes Go output error details as JSON to stdout
		// even on failure. Try to extract the error message from the JSON output.
		if jsonErr := extractGoModDownloadError(b.Bytes()); jsonErr != "" {
			return nil, fmt.Errorf("failed to download module %s@%s: %s: %s", path, version, err, jsonErr)
		}
		return nil, fmt.Errorf("failed to download module %s@%s: %w", path, version, err)
	}

	jsonResult := b.Bytes()

	m := &goModule{}
	if err := json.NewDecoder(bytes.NewReader(jsonResult)).Decode(m); err != nil {
		return nil, fmt.Errorf("failed to decode module download result: %w", err)
	}

	if m.Error != nil {
		return nil, errors.New(m.Error.Err)
	}

	// Cache the successful result if it has a Dir field.
	if m.Dir != "" {
		_ = c.ccfg.ModuleQueriesCache.SetBytes(cacheKey, jsonResult)
	}

	return m, nil
}

// isDirNonEmpty returns true if dir exists and contains at least one file or subdirectory.
func (c *Client) isDirNonEmpty(dir string) bool {
	f, err := c.fs.Open(dir)
	if err != nil {

View on GitHub (pinned to 52c9bd7908)