{"record":{"id":"e2358282194eacf4","repo":"golang/go","slug":"tag-s-found-on-revision-s-is-already-canonical","errorCode":null,"errorMessage":"tag (%s) found on revision %s is already canonical, so should not be replaced with a pseudo-version derived from that tag","messagePattern":"tag \\((.+?)\\) found on revision (.+?) is already canonical, so should not be replaced with a pseudo-version derived from that tag","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmd/go/internal/modfetch/coderepo.go","lineNumber":760,"sourceCode":"\t\treturn nil\n\t} else {\n\t\tfor _, tag := range info.Tags {\n\t\t\tversionOnly := strings.TrimPrefix(tag, tagPrefix)\n\t\t\tif versionOnly == base {\n\t\t\t\t// The base version is canonical, so if the version from the tag is\n\t\t\t\t// literally equal (not just equivalent), then the tag is canonical too.\n\t\t\t\t//\n\t\t\t\t// We allow pseudo-versions to be derived from non-canonical tags on the\n\t\t\t\t// same commit, so that tags like \"v1.1.0+some-metadata\" resolve as\n\t\t\t\t// close as possible to the canonical version (\"v1.1.0\") while still\n\t\t\t\t// enforcing a total ordering (\"v1.1.1-0.[…]\" with a unique suffix).\n\t\t\t\t//\n\t\t\t\t// However, canonical tags already have a total ordering, so there is no\n\t\t\t\t// reason not to use the canonical tag directly, and we know that the\n\t\t\t\t// canonical tag must already exist because the pseudo-version is\n\t\t\t\t// derived from it. In that case, referring to the revision by a\n\t\t\t\t// pseudo-version derived from its own canonical tag is just confusing.\n\t\t\t\treturn fmt.Errorf(\"tag (%s) found on revision %s is already canonical, so should not be replaced with a pseudo-version derived from that tag\", tag, rev)\n\t\t\t}\n\t\t}\n\t}\n\n\ttags, err := r.code.Tags(ctx, tagPrefix+base)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar lastTag string // Prefer to log some real tag rather than a canonically-equivalent base.\n\tancestorFound := false\n\tfor _, tag := range tags.List {\n\t\tversionOnly := strings.TrimPrefix(tag.Name, tagPrefix)\n\t\tif semver.Compare(versionOnly, base) == 0 {\n\t\t\tlastTag = tag.Name\n\t\t\tancestorFound, err = r.code.DescendsFrom(ctx, info.Name, tag.Name)\n\t\t\tif ancestorFound {\n\t\t\t\tbreak","sourceCodeStart":742,"sourceCodeEnd":778,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/cmd/go/internal/modfetch/coderepo.go#L742-L778","documentation":"validatePseudoVersion (coderepo.go:744-762) walks info.Tags looking for one equal to the pseudo-version's base. If a tag with that exact canonical name is found on the very revision the pseudo-version points to, the pseudo-version is redundant and confusing: the canonical tag already exists and already gives a total ordering. The resolver demands that you use the tag directly.","triggerScenarios":"A commit carries tag v1.2.3 and someone references that same commit via a pseudo-version like v1.2.4-0.20240101-e3702bed3d42 (or v1.2.3-0.…). Since v1.2.3 is canonical and on that commit, the pseudo-version is rejected.","commonSituations":"Tooling that always derives pseudo-versions even when a real tag exists; copy-pasting a commit hash instead of the tag that points to it; mirror/proxy that loses tag info and falls back to pseudo-versions.","solutions":["Reference the canonical tag directly in go.mod instead of the pseudo-version.","Run `go get <module>@<tag>` so the tooling picks the tag.","If you are a proxy operator, ensure tags are preserved so clients do not synthesize pseudo-versions."],"exampleFix":"// before (commit also tagged v1.2.3)\nrequire example.com/m v1.2.4-0.20240101120000-e3702bed3d42\n\n// after (use the canonical tag)\nrequire example.com/m v1.2.3","handlingStrategy":"validation","validationCode":"// If the commit is already tagged, prefer the tag.\n// bash:\n//   git tag --points-at <commit>\n//   # if it lists a canonical semver tag, depend on that tag instead\n//\n// In Go: resolve by tag first\n//   go get example.com/m@v1.2.3   // not the pseudo-version for the same commit","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer canonical tags over pseudo-versions whenever a tag exists.","Use `go get <module>@<tag>` instead of `@<commit>` when the tag is known.","Proxy operators: preserve tag metadata so clients do not synthesize redundant pseudo-versions."],"tags":["go-modules","pseudo-version","vcs-tags","canonical-version"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}