8000 staticcheck.conf is silently ignored for packages under os.UserCacheDir · Issue #1847 · dominikh/go-tools · GitHub
[go: up one dir, main page]

Skip to content
< 8000 link rel="preload" as="image" href="https://github.githubassets.com/assets/copilot-e2b89335a66dba00.png"/>

staticcheck.conf is silently ignored for packages under os.UserCacheDir #1847

Open

Description

@mkorun

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

  1. added a commit that references this issue on Nov 4, 2026
    8517578
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0