Skip to content

Dependencies

FieldValue
TypeSkill Resource
Source~/.copilot/skills/backend/references/dependencies.md
DescriptionNot specified

Source Content

Dependencies

A third-party dependency is a maintenance and security liability accepted deliberately, not added reflexively. Every module in go.mod is code your team now ships, patches, and answers for as if you’d written it yourselves — even though you didn’t.

Ask before adding a new one

Upgrading an existing dependency needs no check-in — that’s routine maintenance. Pulling in a new module, especially a new direct dependency, is different: the whole team now transitively trusts its code, its maintainers, and its own dependency tree. Ask before adding a genuinely new third-party dependency to go.mod. A quick “I want to pull in X for Y, alternatives considered were Z” is enough; it just has to happen before the go get, not after.

The tool directive replaces tools.go (Go 1.24+)

Pinning the version of a build-time tool — oapi-codegen (references/openapi.md), golangci-lint — used to mean a tools.go file with a //go:build tools tag and a blank import per tool, purely to convince go mod tidy not to prune it. Go 1.24+ replaces that hack with a first-class tool directive in go.mod.

Terminal window
go get -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
// go.mod (excerpt)
tool (
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
)

Run a pinned tool with go tool <name> — no tools.go, no blank import, no separate build tag to keep in sync.

govulncheck narrows to reachable CVEs

Run govulncheck ./... in CI. Its distinguishing feature over a blanket scanner is that it walks your binary’s actual call graph and reports only the CVEs whose vulnerable function your code can actually reach — not every CVE that exists anywhere in your transitive dependency tree regardless of whether you ever call the affected code.

Terminal window
govulncheck ./...

This is why a CVE can show up in a general-purpose dependency scanner and stay silent in govulncheck: the vulnerable function is in a code path you never call. Cite this when a reviewer flags a scanner alert that govulncheck doesn’t — it isn’t being lax, it’s telling you the exposure doesn’t reach your binary.

go.work for local multi-module development

When developing this service alongside a shared internal library in a sibling checkout, go.work lets local edits in the library be picked up without a replace directive in go.mod or a round-trip through a tagged release.

Terminal window
go work init ./billing ./internal-lib
  • Never commit go.work.sum — it’s a local development artifact, not part of the module’s published dependency graph.
  • Strip any replace directive before publishing or tagging a release; a replace pointing at a local filesystem path breaks the module for every downstream consumer.

CI auto-merge policy for dependency bumps

Automated dependency bumps (Dependabot, Renovate) do not all carry the same risk, so they don’t all get the same review gate:

  • Patch and minor version bumps auto-merge once CI passes green.
  • Major version bumps always require manual review — a major bump can change behavior or break an API this service depends on.
  • Security-patch bumps auto-merge regardless of version-bump size — a security fix waiting on manual review is a window left open on purpose.