We should provide a way for users to prevent go get -u from updating certain dependencies unless they're explicitly requested.

There are a number of reasons users may need to pin a module dependency to a lower version. For example:

  1. The dependency has a breaking change, which is allowed in v0 versions.
  2. There is a cyclic dependency involving the main module, and care must be taken to keep the cycle at specific versions. This is especially important when one module is a prefix of the other, and we want to avoid ambiguous imports as packages are moved between.
  3. The dependency adds many more transitive dependencies that are not needed which may need to be downloaded.
  4. The dependency has new functionality which is not used and adds significant overhead.

When a user has a dependency that requires special care to update, go get -u is not safe to use on its own. Instead, one needs to build a list of all requirements, exclude modules that shouldn't be updated, then pass that list to go get -u. This is cumbersome.

Some ideas for improvements:

  • An -except or -exclude flag, allowing users to specify modules that shouldn't be updated.
  • A comment or annotation in go.mod that tells go get -u not to update a module unless it's specifically named on the command line.

This is somewhat related to #28424.

Comment From: bcmills

The exclude directive in the go.mod file gets us partway there: if there is some regression, such as a large set of new dependencies or a significant performance degradation, then either the maintainers should fix that regression and issue a new release, or the users should fork the module to remove the unwanted bloat. Either way, the need to hold back should only persist for a release or two.

Comment From: bcmills

It may be that we should resolve the cyclic dependency problem in some other way, such as having go get -u detect the cycle and try to upgrade past it (#27899). In particular, we can't stop outside users of a given module from introducing newer requirements, so it seems fragile to rely on the version staying at some older release.

Comment From: bcmills

Finally, if there is a breaking change in a v0 version, then perhaps it's best to have a reminder to fix the call site. (As a workaround, you can always pass an explicit older version of a particular module to go get -u, and having that explicit in the update command is a good indicator of the Damocles' Sword of Incompatibility that is looming over your module...)

Comment From: jayconrod

I don't have a good sense of the severity or frequency of these issues. As you point out, there are alternatives for all of them, but I'm not sure how often they're going to come up or how annoying they're going to be.

Let's mark this as Unplanned and decide what to do after modules are on by default. I don't think this is a critical feature to have before then.

Comment From: bcmills

I think the best fit for this use-case is a special version query. upgrade already means “current selected version or higher”, and patch means “current selected version or higher but within the same minor version”; the logical progression of those is some third token (maybe current?) meaning “current selected version and no higher”.

Comment From: braydonk

What are the chances that this might ever happen in some capacity? I'm in a scenario right now where I want to update all the dependencies in my go.mod except for one due to a breaking change that we are not yet ready to adopt. Effectively, I'd like to pin this dependency to the version we have now while still allowing everything else to update. I cannot find a way to properly accomplish that today.

Comment From: bcmills

@braydonk, today you can pass the version of the package or module that you want to keep to go get -u as an explicit argument, like:

go get -u ./... example.com/stuck@v0.5

Comment From: braydonk

Ah cool, I wasn't able to find that anywhere when I was looking around documentation or even blog posts. Thank you for the trick!

Comment From: flimzy

I'd love to see this capability as well. On one codebase I'm working, we have 3 particular dependencies which introduce breaking changes, so cannot be updated. I'd love the option to do something like go get -pin example.com/foo@v0.5, which I magine might add a // pinned comment after the relevant line in go.mod, so that subsequent go get -u ./... executions just skip the pinned modules.

Comment From: sergeevabc

I have Go 1.23.6. It was not easy to make it work in the first place, because my choice of OS is and will be Windows 7, whereas Go team artificially deprived such users of getting Go and now parrots what Microsoft says: “Oh, obsolete”. Fortunately, Go 1.23.6 is more than enough to compile 99.9% of Go apps out there. I compiled and use dozens without any issues. However, some developers, such as @mwyvr, force users to upgrade Go even more as part of the go install procedure of their apps as follows

$ ver && go version
Microsoft Windows [Version 6.1.7601]
go1.23.6 windows/amd64

$ go install -ldflags="-s -w" github.com/mwyvr/kid/tree/main/cmd/kid@latest
go: downloading github.com/mwyvr/kid v1.2.0
go: github.com/mwyvr/kid@v1.2.0 requires go >= 1.24; switching to go1.24.1
go: downloading go1.24.1 (windows/amd64)

^ CTRL+BREAK, because your damn app can be perfectly compiled with 1.23.6

Is there a switch to stop upgrading Go this way? Or I have to download the source code of the particular app and edit go.mod before building?

$ wget -qO- https://codeload.github.com/mwyvr/kid/zip/refs/heads/main | busybox unzip - -d C:\Go\bin
 Archive:  -
   creating: kid-main/
  inflating: kid-main/LICENSE
  inflating: kid-main/README.md
   creating: kid-main/cmd/
   creating: kid-main/cmd/kid/
  inflating: kid-main/cmd/kid/main.go
   creating: kid-main/eval/
  inflating: kid-main/eval/README.md
   creating: kid-main/eval/bench/
  inflating: kid-main/eval/bench/bench_test.go
   creating: kid-main/eval/compare/
  inflating: kid-main/eval/compare/main.go
   creating: kid-main/eval/uniqcheck/
  inflating: kid-main/eval/uniqcheck/foo
  inflating: kid-main/eval/uniqcheck/main.go
  inflating: kid-main/go.mod
  inflating: kid-main/kid.go
  inflating: kid-main/kid_test.go

$ cd C:\Go\bin\kid-main

$ type go.mod
module github.com/mwyvr/kid

go 1.24

// Package kid has no dependencies outside of the Go standard library.
// If running anything under eval/* run `go mod tidy` to pull in dependencies.

$ busybox sed -i "s/1.24/1.23.6/g" go.mod

$ type go.mod
module github.com/mwyvr/kid

go 1.23.6

// Package kid has no dependencies outside of the Go standard library.
// If running anything under eval/* run `go mod tidy` to pull in dependencies.

$ go build -ldflags="-s -w" .\cmd\kid

$ kid
06bwjx24zr70m3p5