{"record":{"id":"e2b435d11eb19541","repo":"golang/go","slug":"non-canonical-module-version-q","errorCode":null,"errorMessage":"non-canonical module version %q","messagePattern":"non-canonical module version %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd/go/internal/modfetch/cache.go","lineNumber":60,"sourceCode":"\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(cfg.GOMODCACHE, \"cache/download\", enc, \"/@v\"), nil\n}\n\nfunc CachePath(ctx context.Context, m module.Version, suffix string) (string, error) {\n\tif gover.IsToolchain(m.Path) {\n\t\treturn \"\", ErrToolchain\n\t}\n\tdir, err := cacheDir(ctx, m.Path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !gover.ModIsValid(m.Path, m.Version) {\n\t\treturn \"\", fmt.Errorf(\"non-semver module version %q\", m.Version)\n\t}\n\tif module.CanonicalVersion(m.Version) != m.Version {\n\t\treturn \"\", fmt.Errorf(\"non-canonical module version %q\", m.Version)\n\t}\n\tencVer, err := module.EscapeVersion(m.Version)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(dir, encVer+\".\"+suffix), nil\n}\n\n// DownloadDir returns the directory to which m should have been downloaded.\n// An error will be returned if the module path or version cannot be escaped.\n// An error satisfying errors.Is(err, fs.ErrNotExist) will be returned\n// along with the directory if the directory does not exist or if the directory\n// is not completely populated.\nfunc DownloadDir(ctx context.Context, m module.Version) (string, error) {\n\tif gover.IsToolchain(m.Path) {\n\t\treturn \"\", ErrToolchain\n\t}\n\tif err := checkCacheDir(ctx); err != nil {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/cmd/go/internal/modfetch/cache.go#L42-L78","documentation":"Thrown by `modfetch.CachePath` when the module version passes `ModIsValid` but `module.CanonicalVersion(version) != version`. Canonical versions have no redundant components (e.g., 'v1.2.0' not 'v1.2', no '+build' metadata that's identical to base). The cache requires canonical versions so every module has exactly one cache location.","triggerScenarios":"Calling CachePath with a version like 'v1.2' (should be 'v1.2.0'), 'v1.0.0+meta' where the metadata is non-significant, or 'v1.00.0' (non-canonical zero-padding). CanonicalVersion normalizes these, and if the result differs, the error fires.","commonSituations":"Receiving non-canonical versions from external sources (proxies, manifests); user-supplied versions that aren't normalized; bugs in version string construction in calling code.","solutions":["Canonicalize the version before calling CachePath: `version = module.CanonicalVersion(version)`","Use module.EscapeVersion which also expects canonical input — canonicalize upstream","Validate with `module.CanonicalVersion(v) == v` before calling cache APIs"],"exampleFix":"// before\npath, err := modfetch.CachePath(ctx, module.Version{Path: m, Version: \"v1.2\"}, \"zip\")\n// after\ncanon := module.CanonicalVersion(\"v1.2\")\npath, err := modfetch.CachePath(ctx, module.Version{Path: m, Version: canon}, \"zip\")","handlingStrategy":"validation","validationCode":"import \"golang.org/x/mod/module\"\n\nfunc canonicalizeVersion(v string) string {\n    return module.CanonicalVersion(v)\n}\n\n// Before calling CachePath:\nv = module.CanonicalVersion(v)\nif v != originalV {\n    // version was non-canonical, now fixed\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass module.CanonicalVersion(v) to cache APIs","Normalize version strings at the boundary where they enter your code","Use versions returned by the module resolution pipeline, which are pre-canonicalized"],"tags":["go","go-mod","version","canonicalization","internal-api"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}