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
- 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.
- 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.
- Clear the module cache and retry: `go clean -modcache && hugo mod clean` then rebuild.
- 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
- Pin module versions to tags you control or that are proven fetchable.
- Set GOPRIVATE/GOPROXY in the build env before invoking Hugo for private modules.
- Run `hugo mod tidy` in CI as a pre-step to fail fast on unreachable modules.
- Keep the Go toolchain version within the range your Hugo release supports.
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
- failed to download module
- failed to download modules
- failed to decode module download result
- failed to decode modules list
- failed to list modules
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)