golang/go · error
version %s is not canonical
Error message
version %s is not canonical
What it means
Returned by codeRepo.GoMod when the version argument is not equal to module.CanonicalVersion(version). Canonical form strips leading zeros and redundant segments (v1.2.3, v1.2.3-pre, v1.2.3+build); anything else is rejected because every downstream cache key assumes canonicalization.
Source
Thrown at src/cmd/go/internal/modfetch/coderepo.go:970
// path might replace a module with path gopkg.in/foo.v2-unstable, and that's
// ok.
return pathMajor[1:] == mpathMajor[1:]
}
// canReplaceMismatchedVersionDueToBug reports whether versions of r
// could replace versions of mpath with otherwise-mismatched major versions
// due to a historical bug in the Go command (golang.org/issue/34254).
func (r *codeRepo) canReplaceMismatchedVersionDueToBug(mpath string) bool {
// The bug caused us to erroneously accept unversioned paths as replacements
// for versioned gopkg.in paths.
unversioned := r.pathMajor == ""
replacingGopkgIn := strings.HasPrefix(mpath, "gopkg.in/")
return unversioned && replacingGopkgIn
}
func (r *codeRepo) GoMod(ctx context.Context, version string) (data []byte, err error) {
if version != module.CanonicalVersion(version) {
return nil, fmt.Errorf("version %s is not canonical", version)
}
if module.IsPseudoVersion(version) {
// findDir ignores the metadata encoded in a pseudo-version,
// only using the revision at the end.
// Invoke Stat to verify the metadata explicitly so we don't return
// a bogus file for an invalid version.
_, err := r.Stat(ctx, version)
if err != nil {
return nil, err
}
}
rev, dir, gomod, err := r.findDir(ctx, version)
if err != nil {
return nil, err
}
if gomod != nil {View on GitHub (pinned to b6b368adc5)
Solutions
- Canonicalize before calling: pass module.CanonicalVersion(version) (or reject early if it differs).
- If the version comes from a VCS tag, normalize tags with module.CanonicalVersion and skip non-semver tags.
- Reject non-canonical input at the API boundary of your tool rather than letting it reach GoMod.
Example fix
// before
repo.GoMod(ctx, "v1.2")
// after
v := module.CanonicalVersion("v1.2")
repo.GoMod(ctx, v) Defensive patterns
Strategy: validation
Validate before calling
v := module.CanonicalVersion(version)
if v != version {
return fmt.Errorf("refusing non-canonical version %q (canonical %q)", version, v)
}
return repo.GoMod(ctx, v) Type guard
// isCanonicalVersion narrows a version string already confirmed canonical.
func isCanonicalVersion(v string) bool {
return v != "" && v == module.CanonicalVersion(v)
} Prevention
- Never hand-build version strings; route them through module.CanonicalVersion.
- Reject non-canonical versions at the boundary of your tool before any modfetch call.
When it happens
Trigger: An internal caller hands GoMod a version such as "v1.2", "v1.02.3", or a pseudo-version with extra characters. The check `version != module.CanonicalVersion(version)` fails on the first line of GoMod before any I/O.
Common situations: A wrapper tool or test constructs versions by string concatenation; a manual call to modfetch on a tag list without canonicalizing; upgrading code that previously tolerated loose semver.
Related errors
- ${GoModToolVersion} is required for tool directives in go.mo
- disallowed module version
- missing %s/go.mod at revision %s
- Go.run: WebAssembly.Instance expected
- archive/tar: FileInfo is nil
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/0be21146fdfbadfa.
Report an issue: GitHub.