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

  1. Canonicalize before calling: pass module.CanonicalVersion(version) (or reject early if it differs).
  2. If the version comes from a VCS tag, normalize tags with module.CanonicalVersion and skip non-semver tags.
  3. 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

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


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/0be21146fdfbadfa. Report an issue: GitHub.