The go command chooses its module mode from your repository. If go.mod declares Go 1.14 or higher and a vendor directory exists, it acts as if -mod=vendor were set. Otherwise it acts as if -mod=readonly were set. Run your SBOM job in the same mode as your release build, so the file describes what you shipped.
What each mode means
The Go modules reference describes the defaults and the flags.
| Mode | How you get it | Effect |
|---|---|---|
| vendor | Default when vendor exists and the go version in go.mod is 1.14 or higher, or by passing the flag | Packages load from the vendor directory, with no network or module cache access |
| readonly | Default otherwise, or by passing the flag | The go command will not update go.mod on its own |
| mod | Passed explicitly | Allows the go command to update go.mod |
The reference also says that to disable vendoring you pass readonly or mod, and that when vendoring is enabled, build commands such as go build and go test load packages from vendor. Commands such as go mod download and go mod tidy do not change behavior under vendoring and still use the module cache.
Why an SBOM job cares
An SBOM job usually runs a command that lists modules, or inspects a built binary. If the job runs in a different mode than the release build, it may describe different inputs. Three examples:
- A release build uses vendor by default, but the SBOM job passes the mod flag. The job resolves modules from the cache and network and may pick up a version the vendored build never used.
- A job runs go mod tidy before listing modules. Tidy can change go.mod, so the list may no longer match the commit you are about to tag.
- A job runs in readonly mode with no vendor directory and the lockfile equivalent, go.sum, is missing entries. The command fails, which is better than silently guessing.
A worked example
A team has a service whose go directive says 1.21 and a vendor directory committed to the repository. This is a made up setup.
Step one: the release pipeline runs go build with default settings. Because the directory exists and the version is 1.14 or higher, the go command uses vendor. The build never touches the network.
Step two: the SBOM job is configured with the same working directory and no mode flag, so it inherits the same default. It lists modules, and because vendoring is on, the version information comes from vendor/modules.txt, as the reference says is used by go list with the module flag and go version with the module flag.
Step three: someone proposes adding the mod flag to the SBOM job "to be safe". The reviewer asks what it changes. Answer: it turns vendoring off for that job, so the job might describe a different dependency set than the build. The proposal is rejected.
A decision guide for the job
| Your repository | Recommended SBOM job mode |
|---|---|
| Vendor directory committed, go 1.14 or higher | Default, which is vendor |
| No vendor, go.sum committed | Default, which is readonly |
| Older go directive with a vendor directory | Pass the vendor flag explicitly, or upgrade the directive |
| Release build passes a flag | Pass the same flag |
The principle is simple: copy the release build's mode exactly, and put the flag in one shared place so the two cannot drift.
What to verify after the run
- Compare the module list in the SBOM with go.mod. Explain any difference.
- If vendoring is on, compare with vendor/modules.txt.
- Run a build with default settings and confirm it passes. The reference says the go command checks that vendor/modules.txt is consistent with go.mod and reports an error if go.mod has changed since vendoring.
- Record the Go version and the mode in the SBOM metadata or your build notes.
Common mistakes
Bumping the go directive without noticing the side effect. Raising it to 1.14 or higher in a repository that has a stale vendor directory turns on automatic vendoring and may surface a consistency error. That is a good error to meet in a pull request and a bad one to meet at release time.
Running the SBOM job from the wrong directory. The vendor directory must be in the main module root to be used.
Assuming test solely imports are covered. The reference notes that packages imported solely by tests of packages outside the main module are not vendored, so a list built from vendor may omit them, and that is expected.
Keeping it honest
Write one sentence in your SBOM policy: the job runs in the same Go module mode as the release build. Then add a test in CI that fails if the two commands are configured differently. Safeguard can scan the resulting SBOM and dependencies, and the free scan at gold.safeguard.sh/scan is an easy way to check a Go project.
Documenting the contract
Put the contract where people will find it. A short section in your build documentation can say which Go version the release uses, whether vendor is committed, which module mode the build runs in, and that the SBOM job must match. New team members often meet these defaults for the first time during an incident, and a written contract turns a mystery into a lookup.
Testing the contract
A cheap test is to run the SBOM job and the release build in a scratch environment and compare module lists. If they match, record the result. If they differ, find which flag or directory explains the difference. Run this test whenever you change the Go version, the vendor policy or the CI image, because those are the moments when defaults shift.
Try it yourself
Click through it at your own pace, or open it full screen.
Open a Go component and compare the version shown with what your own module files say.
Quick check
What is the default mode when a vendor directory exists?
If the go version in go.mod is 1.14 or higher, the go command acts as if -mod=vendor were used.
Does go mod tidy read from vendor?
No. The reference says go mod download and go mod tidy do not change behavior under vendoring and still use the module cache.
How do I turn vendoring off?
Pass -mod=readonly or -mod=mod, per the Go modules reference.
Sources
Last reviewed: 19 October 2026.