Go version

go version go1.25.0 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/adamhamrick/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/adamhamrick/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/2l/06jbymcj3mn7yp510wxm_jq40000gn/T/go-build4290062372=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/adamhamrick/Projects/branch-out/golang/example_project/go.mod'
GOMODCACHE='/Users/adamhamrick/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/adamhamrick/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.25.0/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/adamhamrick/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.25.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.25.0'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I'm writing a simple program to discover and load basic information about all packages in a Go project. This code normally works fine.

func loadPackagesFromModule(moduleDir string) {
    config := &packages.Config{
        Mode:  packages.NeedName | packages.NeedModule | packages.NeedFiles,
        Dir:   moduleDir,
        Tests: true,
    }

    pkgs, err := packages.Load(config, "./...")
    if err != nil {
        return nil, fmt.Errorf("failed to load packages from module %s: %w", moduleDir, err)
    }
}

What did you see happen?

I'm dealing with a project that has build tags in many of their files. When I run the above code on this project:

.
├── go.mod
├── go.sum
├── main.go  * Has build tag
├── package1
│        └── other1.go  * Has build tag
│        └── other1_test.go  * Has build tag
├── package2
│        └── other2.go  * Has build tag
│        └── other2_test.go  * Has build tag
├── base.go  * Has build tag
├── base_test.go  * Has build tag

the code returns only the main package and its files in IgnoredFiles.

What did you expect to see?

I can get it to properly Load all packages if I provide BuildFlags: []string{"-tags", "build_flag"}, but I thought the whole point of IgnoredFiles was to find files and packages that might have been ignored thanks to build tags. I wouldn't expect only the main package to be loaded in IgnoredFiles and all the rest to never be touched.

Is this a bug? Or am I misunderstanding how this is intended to work? Is there any way to fully Load all packages, agnostic of build tags?

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: cherrymui

cc @adonovan @matloob

Comment From: IlyasYOY

Hey @kalverra,

I think the behavior you’re seeing is related to how go list (and therefore packages.Load) treats a package whose own files are excluded by build constraints. When the base package is considered empty, the tool stops recursing into its subdirectories, so those sub‑packages never get examined.

I reproduced a similar layout and observed the same pattern:

➜  testgolist go list .
package github.com/ilyasyoy/testgolist: build constraints exclude all Go files in /Users/ilyasyoy/Projects/IlyasYOY/testgolist
➜  testgolist go list ./...
go: warning: "./..." matched no packages
➜  testgolist go list -tags test .
github.com/ilyasyoy/testgolist
➜  testgolist go list -tags test ./...
github.com/ilyasyoy/testgolist
github.com/ilyasyoy/testgolist/package1
github.com/ilyasyoy/testgolist/package2
➜  testgolist tree
.
├── file1.go
├── go.mod
├── package1
│   └── file1.go
└── package2
    └── file2.go

3 directories, 4 files

So, when the root package is filtered out by the default tags, the entire tree is effectively invisible to packages.Load.

Does this line up with your observations?

UPD. I guess the answer is in this function: https://github.com/golang/go/blob/80038586ed2814a03dcb95cd6f130766f8d803c3/src/cmd/go/internal/modload/load.go#L253

Comment From: kalverra

@IlyasYOY Yes, this matches my observations. I suppose this is a bit of a niche case, but it strikes me as a bug.