Cookbook
On this page
Copyable one-liners for the Go tools in your $GOBIN. Every recipe runs as
shown; swap the tool names for yours.
go install drops a binary in $GOBIN and then forgets it. gup reads the build
info stamped into each binary — import path, version, Go toolchain — so the set
you already installed is the manifest, and there is nothing to declare first.
Find a recipe by task #
| I want to | Go to |
|---|---|
See what go install left in my $GOBIN | See what you have |
| Find out what is out of date | Find what is out of date |
| Update the whole set at once | Update everything |
| Update some tools and skip others | Update only some tools |
| Keep one tool on an exact version | Hold a tool at a version |
Track @main instead of a release | Follow a branch |
| Install the same tools on another machine | Reproduce the set elsewhere |
| Move my tools after a Go upgrade | Move to a new $GOBIN |
| Delete a tool I no longer use | Remove a tool |
| Drive gup from a script or from CI | Script it |
| Work out why an update failed | When an update fails |
| Get tab completion and man pages | Completion and man pages |
See what you have #
Every binary under $GOBIN, with the import path and version it was built from:
gup list

The same as JSON, one object per tool:
gup list --json
[
{
"name": "gup",
"import_path": "github.com/nao1215/gup",
"module_path": "github.com/nao1215/gup",
"channel": "latest",
"current_version": "v1.0.0",
"current_go_version": "go1.26.4",
"installed_go_version": "go1.26.4",
"status": "installed"
}
]
Just the names, for the next command in the pipe:
gup list --json | jq -r '.[].name'
A binary that was not installed by go install is reported, not hidden — it
simply has no import path to reinstall from.
Find what is out of date #
check compares each tool against its module and against the Go toolchain you
are running now. It never installs anything:
gup check
Only the tools that need something, plus a one-line summary:
gup check --quiet
github.com/nao1215/gup (current: v0.7.0, latest: v0.7.1 / go1.26.4)
If you want to update binaries, run the following command.
$ gup update gup
gup: 1 update available, 8 up-to-date, 0 failed
Check two tools instead of the whole set:
gup check gopls staticcheck
The names of everything with an update waiting:
gup check --json | jq -r '.[] | select(.status == "update-available") | .name'
A tool built with an older Go toolchain counts as out of date, because it is. To ignore that and compare versions only:
gup check --ignore-go-update
check exits 0 even when it finds updates — finding them is the job, not a
failure.
Update everything #
gup update

gup runs the updates in parallel, so the whole set finishes in about the time the slowest build takes.
See what would happen first:
gup update --dry-run
Version updates only, leaving Go-toolchain rebuilds alone:
gup update --ignore-go-update
Fewer parallel builds, and a ceiling on any single one:
gup update --jobs 4 --timeout 5m
Tell me when the long run is done:
gup update --notify
Update only some tools #
Name them:
gup update gopls staticcheck
Or update everything except the ones you name:
gup update --exclude gopls,golangci-lint
--exclude combines with --dry-run, so you can confirm the skip list before
anything is built:
gup update --exclude gopls --dry-run
Hold a tool at a version #
Pin the tool your CI is pinned to, then keep updating everything else:
gup pin golangci-lint v1.62.0
gup update
The tool@version form works too:
gup pin golangci-lint@v1.62.0
A pinned tool is installed as go install <import_path>@<version> and is never
resolved to @latest. The pin lives in gup.json under channel: "pinned", so
it survives export/import.
Which tools are pinned, and to what:
gup check --json | jq -r '.[] | select(.channel == "pinned") | "\(.name) \(.pinned_version)"'
gup check reports a pinned tool as pinned when it sits at its version, and
pin-mismatch when something moved it. Let it float again with:
gup unpin golangci-lint
Follow a branch #
Some tools ship from a branch, not a tag:
gup update --main gup
gup update --master sqly
gup update --latest air
--main falls back to @master only when the repository has no main branch.
A build failure on @main is reported as a build failure; it never silently
installs @master instead.
The channel is written to gup.json and reused by later runs, so you set it
once:
gup update --main gup,lazygit --master sqly --latest air
After that, a plain gup update keeps each tool on the channel you chose.
Reproduce the set elsewhere #
On the machine that has the tools:
gup export
On the machine that wants them:
gup import
export writes $XDG_CONFIG_HOME/gup/gup.json; import reads it (or ./gup.json),
and installs the exact version recorded for each tool. Pinned tools stay pinned.
Keep the file in your dotfiles instead:
gup export --output > gup.json
gup import --file gup.json
Check what an import would install before it installs it:
gup import --file gup.json --dry-run
Move to a new $GOBIN #
When a Go upgrade changes where $GOBIN points — this happens with
mise on every Go version — the old tools are still on
disk, just invisible to the new toolchain. Reinstall them into the new
directory, at the versions they already had:
gup migrate ~/.local/share/mise/installs/go/1.24.0/bin ~/.local/share/mise/installs/go/1.25.0/bin
Only some of them:
gup migrate /old/gobin /new/gobin gopls air
migrate is add-only: it never deletes anything in the destination, and it
skips a binary that is already there. Look before you leap, then overwrite on
purpose:
gup migrate /old/gobin /new/gobin --dry-run
gup migrate /old/gobin /new/gobin --force
Local devel builds and binaries with no resolvable version are skipped rather
than upgraded, so nothing you built by hand is quietly replaced.
Remove a tool #
remove asks before each deletion:
gup remove subaru ubume
gup:CHECK: remove /home/nao/.go/bin/subaru? [Y/n] Y
removed /home/nao/.go/bin/subaru
Do not ask:
gup remove --force gal
With no terminal to ask on — a pipe, a CI job — remove fails fast instead of
blocking forever on a prompt nobody can answer. Pass --force there.
Script it #
list, check, and update all take --json, and the array stays valid JSON
even when some packages fail (those get "status": "error"):
gup check --json > check.json
Report only the failures, with their message:
gup update --json | jq -r '.[] | select(.status == "error") | "\(.name): \(.error)"'
Fail a CI job when anything is behind:
test -z "$(gup check --json | jq -r '.[] | select(.status == "update-available") | .name')"
Errors always go to STDERR, so STDOUT stays pure JSON and can be piped straight
into jq.
For human-readable logs, drop the noise and the escape codes:
gup update --quiet --no-color
NO_COLOR=1 gup update
An empty $GOBIN is a normal first run, not an error: list, check, and
update exit 0 (and print [] with --json).
When an update fails #
gup turns the Go toolchain’s output into one next step, printed on STDERR after the error:
gup:ERROR: [1/1] tool: can't install gup.test/moved/cmd/tool:
go: gup.test/moved/cmd/tool@latest: module gup.test/moved@latest found (v1.1.0), but does not contain package gup.test/moved/cmd/tool
gup:HINT : The module no longer provides this command at its import path. The project likely moved to a new major version (e.g. a `/v2` module path) or relocated the command; check its current install instructions and reinstall with the new path.
The same text is the hint field under --json, so a script can collect them:
gup update --json | jq -r '.[] | select(.hint) | "\(.name): \(.hint)"'
Hints cover major-version moves, relocated commands, replace directives,
binaries that never came from go install, missing branches and tags,
unreachable or private repositories, permission and network errors, and a Go
toolchain that is too old. When gup has nothing reliable to add, it says
nothing.
A failure is per-package: the other tools in the run still update, and the process exits non-zero so CI notices.
Completion and man pages #
Install completion for the shell you are in:
gup completion --install
Or print it and place it yourself:
gup completion bash > gup.bash
gup completion zsh > _gup
gup completion fish > gup.fish
gup completion powershell > gup.ps1
Completion is not just subcommands: gup update, gup remove, and gup pin
complete the binary names actually present in your $GOBIN.
Man pages, on Linux and macOS:
sudo gup man
man honors MANPATH when it is set, and fails with a clear error instead of a
stack trace when the target directory is not writable.