Repository navigation
Description
A package anywhere below the user cache directory (~/.cache on Linux) gets no staticcheck.conf: staticcheck runs it with the default checks and says nothing. We hit it with git worktrees kept under ~/.cache, where a check our configuration turns off failed the build.
Reproduce (staticcheck 2026.2.2 (0.8.2) from go install …@latest, go1.27.3 linux/amd64, a fresh HOME):
mkdir -p ~/.cache/repro && cd ~/.cache/repro
printf 'module example.com/repro\n\ngo 1.24\n' > go.mod
printf 'package repro\n\nimport "errors"\n\nvar ErrX = errors.New("Capitalized message")\n' > repro.go
printf 'checks = ["inherit", "-ST1005"]\n' > staticcheck.conf
staticcheck ./...
# repro.go:5:12: error strings should not be capitalized (ST1005)The same three files, elsewhere and in other settings:
| where | setting | ST1005 |
|---|---|---|
~/repro |
no | |
~/.other/repro |
no | |
~/.cache/repro |
yes | |
~/.cache/repro |
STATICCHECK_CACHE fresh, elsewhere |
yes |
~/.cache/repro |
XDG_CACHE_HOME elsewhere, STATICCHECK_CACHE and GOCACHE unchanged |
no |
~/repro |
XDG_CACHE_HOME=$HOME |
yes |
~/.cache-like/repro |
yes | |
~/noconf, no staticcheck.conf (control) |
yes |
So it is neither the result cache nor a dot directory: it is the user cache directory as a string prefix.
Cause: config.Dir, called by go/loader with pkg.GoFiles, skips every file whose path starts with os.UserCacheDir(), taking it for a cgo file in Go's build cache (6be3e97, 2019). A package whose files all lie below that directory, or below any path that merely starts with the same string, ends up with DefaultConfig.
What the check guards: the test main package that go list -test synthesizes (example.com/repro.test) has its one file in the build cache, and the check keeps the configuration lookup out of there. (cgo packages do not need it any more: pkg.GoFiles names their original sources.) But it recognises the build cache by the user cache directory, so it catches far more than the build cache, and misses the build cache whenever GOCACHE is set elsewhere.
Expected: the staticcheck.conf of a package's directory tree applies wherever the package lives, as the documentation describes.
Suggestion: compare against Go's build cache itself (go env GOCACHE) rather than os.UserCacheDir(), and on a path boundary rather than a string prefix, as the FIXME beside it notes.
Tried on Linux only. The same code reads ~/Library/Caches on macOS and %LocalAppData% on Windows.
A script that runs every case above in a fresh, empty HOME
Usage: GO=$(command -v go) bash staticcheck-cache-dir.sh <new empty directory>
#!/usr/bin/env bash
# staticcheck and staticcheck.conf under os.UserCacheDir: each experiment in a fresh HOME, staticcheck from go install @latest
set -u
GO=${GO:-$(command -v go)}
H=${1:?usage: staticcheck-cache-dir.sh <empty directory to use as HOME>} # the fresh account; its own ~/.cache
mkdir -p "$H" && [ -z "$(ls -A "$H")" ] || { echo "$H must be a new or empty directory" >&2; exit 2; }
base_env=(env -i "HOME=$H" "PATH=$(dirname "$GO"):/usr/bin:/bin" "GOPATH=$H/go" "GOFLAGS=-modcacherw" "GOTOOLCHAIN=local")
"${base_env[@]}" GOBIN="$H/bin" timeout 300 "$GO" install honnef.co/go/tools/cmd/staticcheck@latest || exit 9
SC="$H/bin/staticcheck"
"${base_env[@]}" "$SC" -version
project() { # dir, with or without staticcheck.conf
mkdir -p "$1"
printf 'module example.com/repro\n\ngo 1.24\n' > "$1/go.mod"
printf 'package repro\n\nimport "errors"\n\nvar ErrX = errors.New("Capitalized message")\n' > "$1/repro.go"
[ "${2:-conf}" = conf ] && printf 'checks = ["inherit", "-ST1005"]\n' > "$1/staticcheck.conf"
return 0
}
run() { # label, dir, extra env...
local label=$1 dir=$2; shift 2
local out; out=$(cd "$dir" && "${base_env[@]}" "$@" timeout 120 "$SC" ./... 2>&1); local code=$?
printf '%-58s exit %s %s\n' "$label" "$code" "$(echo "$out" | grep -o 'ST1005' | head -1)"
}
project "$H/repro"; project "$H/.cache/repro"
project "$H/noconf" noconf; project "$H/.other/repro"; project "$H/.cache-like/repro"
run "K without staticcheck.conf, ~/noconf (control)" "$H/noconf"
run "1 ~/repro" "$H/repro"
run "2 ~/.other/repro (a dot directory)" "$H/.other/repro"
run "3 ~/.cache/repro" "$H/.cache/repro"
run "3b ~/.cache/repro again" "$H/.cache/repro"
run "A ~/.cache/repro, results cache fresh and elsewhere" "$H/.cache/repro" STATICCHECK_CACHE="$H/fresh-sc-cache"
run "B ~/.cache/repro, XDG_CACHE_HOME moved, caches kept" "$H/.cache/repro" XDG_CACHE_HOME="$H/xdg" STATICCHECK_CACHE="$H/.cache/staticcheck" GOCACHE="$H/.cache/go-build"
run "C ~/repro, XDG_CACHE_HOME=~/ (now a prefix of it)" "$H/repro" XDG_CACHE_HOME="$H" STATICCHECK_CACHE="$H/.cache/staticcheck" GOCACHE="$H/.cache/go-build"
run "D ~/.cache-like/repro (prefix ~/.cache, not inside)" "$H/.cache-like/repro" 2>/dev/null || true
# cgo: where the generated files lie, and the other side, a GOCACHE outside the user cache directory
cgo() {
project "$1"
printf 'package repro\n\n// int one(void) { return 1; }\nimport "C"\n\nfunc One() int { return int(C.one()) }\n' > "$1/c.go"
}
cgo "$H/cgo"
echo "--- cgo"
(cd "$H/cgo" && "${base_env[@]}" CGO_ENABLED=1 "$GO" list -compiled -f '{{range .CompiledGoFiles}}{{println .}}{{end}}' . | sed "s|$H|~|")
run "E ~/cgo, cgo, default GOCACHE (~/.cache/go-build)" "$H/cgo" CGO_ENABLED=1
run "F ~/cgo, cgo, GOCACHE=~/gocache (outside ~/.cache)" "$H/cgo" CGO_ENABLED=1 GOCACHE="$H/gocache"
# a package of cgo files alone: nothing but generated files in the compiled list
mkdir -p "$H/cgoonly"; cp "$H/cgo/go.mod" "$H/cgo/staticcheck.conf" "$H/cgo/c.go" "$H/cgoonly/"
printf 'package repro\n\n// int one(void) { return 1; }\nimport "C"\nimport "errors"\n\nvar ErrX = errors.New("Capitalized message")\n\nfunc One() int { return int(C.one()) }\n' > "$H/cgoonly/c.go"
echo "--- cgo only"
(cd "$H/cgoonly" && "${base_env[@]}" CGO_ENABLED=1 "$GO" list -compiled -f '{{range .CompiledGoFiles}}{{println .}}{{end}}' . | sed "s|$H|~|")
run "G ~/cgoonly, default GOCACHE" "$H/cgoonly" CGO_ENABLED=1
run "H ~/cgoonly, GOCACHE=~/gocache (outside ~/.cache)" "$H/cgoonly" CGO_ENABLED=1 GOCACHE="$H/gocache"
# what the check does guard: the test main package that go list -test synthesizes has its one file in the build cache
printf 'package repro\n\nimport "testing"\n\nfunc TestX(t *testing.T) {}\n' > "$H/repro/repro_test.go"
echo "--- go list -test in ~/repro"
(cd "$H/repro" && "${base_env[@]}" "$GO" list -test -f '{{.ImportPath}}: {{range .GoFiles}}{{.}} {{end}}' ./... | sed "s|$H|~|g")
run "I ~/repro with a test" "$H/repro"Environment (the fresh HOME shown as /home/user)
$ staticcheck -version
staticcheck 2026.2.2 (0.8.2)
$ staticcheck -debug.version
staticcheck 2026.2.2 (0.8.2)
Compiled with Go version: go1.27.3
Main module:
honnef.co/go/tools@v0.8.2 (sum: h1:+jPbCzuYxYrcoOvCecYUFDj+XJQRmzIhJmlbpps21Zk=)
Dependencies:
github.com/BurntSushi/toml@v1.4.1-0.20240526193622-a339e1f7089c (sum: h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=)
golang.org/x/exp/typeparams@v0.0.0-20231108232855-2478ac86f678 (sum: h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=)
golang.org/x/mod@v0.41.0 (sum: h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=)
golang.org/x/sync@v0.23.0 (sum: h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=)
golang.org/x/tools@v0.51.0 (sum: h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=)
$ go version
go version go1.27.3 linux/amd64
$ go env
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN='/home/user/go/bin'
GOCACHE='/home/user/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/user/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS='-modcacherw'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3491629520=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/home/user/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPACKAGESDRIVER=''
GOPATH='/home/user/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/user/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.27.3'
GOWORK=''
PKG_CONFIG='pkg-config'
Activity
config: compare files against GOCACHE, not the user cache dir