gohugoio/hugo · error

failed to download module

Error message

failed to download module %s@%s: %s: %s

What it means

Thrown by downloadModuleVersion after `go mod download -json path@version` exits non-zero. Hugo asks Go for structured error output via the -json flag and extractGoModDownloadError pulls the `Error` field out of the JSON; this variant is used when that extraction succeeds, so the message carries both the raw go-command error and the parsed JSON error (typically the real cause: 404, dial, 410 gone, sum mismatch, auth). It means the module fetch genuinely failed at the Go toolchain level, not a Hugo-side bug.

Solutions

  1. Inspect the embedded JSON error (second %s) — for '404/410 Gone' confirm the module path and tag exist on the remote; for 'unknown revision' edit go.mod to a valid branch/tag.
  2. For private modules set GOPRIVATE and check GOPROXY: `go env -w GOPRIVATE=github.com/yourorg/*` and verify `go mod download path@version` works outside Hugo.
  3. Clear the module cache and retry: `go clean -modcache && hugo mod clean` then rebuild.
  4. If the proxy is unreachable, configure a working GOPROXY (e.g. company mirror) or set GONOSUMCHECK/GONOSUMDB appropriately for private checksums.

Example fix

// before (hugo.toml)
[module]
[[module.imports]]
path = "github.com/myorg/secret-theme"
version = "v1.0.0"

// after: tell the toolchain the module is private + reachable
go env -w GOPRIVATE=github.com/myorg/*
go env -w GOPROXY=https://goproxy.company.io,direct
Defensive patterns

Strategy: validation

Validate before calling

// Validate reachability + privacy before letting Hugo download
import "os/exec"

func checkModuleFetchable(path, version string) error {
  // 1. go binary present
  if _, err := exec.LookPath("go"); err != nil {
    return fmt.Errorf("go toolchain missing: %w", err)
  }
  // 2. reproducible probe
  cmd := exec.Command("go", "mod", "download", "-json", path+"@"+version)
  out, err := cmd.CombinedOutput()
  if err != nil {
    return fmt.Errorf("preflight download failed: %s", out)
  }
  return nil
}

Type guard

null

Try / catch

// At the call site that drives module collection (e.g. hugofs/modules client):
// err := c.downloadModuleVersion(path, version)
// if err != nil {
//   if strings.Contains(err.Error(), "failed to download module") {
//     // surface the embedded JSON error, fall back to a known-good version
//   }
//   return err
// }

Prevention

When it happens

Trigger: A module import in hugo.toml/go.mod with an explicit version (e.g. `[[module.imports]] path="github.com/x/y" version="v1.2.3"`) where `go mod download -json github.com/x/y@v1.2.3` fails and its JSON output contains an Error object. Happens on first build, after `hugo mod clean`, or when the GOPROXY/GONOSUMCHECK setup can't reach the proxy.

Common situations: Private repo without GOPRIVATE set (proxy returns 404/410), module deleted or force-pushed away (unknown revision), version tag yanked, corporate proxy/firewall blocking proxy.golang.org, GOFLAGS=-mod=vendor conflict, or a typo'd module path that resolves to a real 404 from the proxy.

Related errors


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

Appendix: source

Thrown at modules/client.go:464

	if b, _ := c.ccfg.ModuleQueriesCache.GetBytes(cacheKey); len(b) > 0 {
		var cm goModule
		if err := json.Unmarshal(b, &cm); err == nil && cm.Dir != "" {
			if c.isDirNonEmpty(cm.Dir) {
				return &cm, nil
			}
		}
	}

	// 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)

View on GitHub (pinned to 52c9bd7908)