2.1.4 Common Dependency Issues

recommended

The target graph from 2.1.1 Nodes, Edges & Acyclicity is simple, but the failures it produces are often indirect. A build can stay green even while its dependency declarations drift away from the code's real needs, or it can break only after two branches of the graph force incompatible versions of the same external library. The recurring theme is not "Bazel is confused". It is that the graph is telling two different stories: what the code actually uses and what the build metadata or version policy says it uses.1,2,3

1.1.10 Common Error Messages shows how to distinguish Bazel label errors from compiler missing-symbol output and provides a runnable missing-dependency example. Here, the same symptom becomes a graph diagnosis: missing direct edges, accidental re-exports, and version diamonds.

Three dependency problems: what the graph shows and how to fix it
Read each card from the concrete problem, through its dependency paths, to the repair.
Diamond
Branches pin incompatible versions
Same library, different version picks
Graph evidence
A
B C
D 1.x D 2.x ×
Fix Choose one compatible version of D for the build.
Stale direct
Real use skips the direct dep line
BUILD names B, imports reach C
Graph evidence
A B C
A C
deps = [":B"] only — real import omitted
Fix Add C to A's direct deps.
Re-export
A receives C through B
A breaks if B stops forwarding C
Graph evidence
A B C
A uses C, but its BUILD metadata names only B
Fix Make B an intentional public facade, or declare A → C directly.

Diamond dependencies become version problems

A diamond dependency is the shape where A depends on B and C, and both of those depend on D. The hard version of the problem is not the shape by itself, but the moment the two branches want incompatible versions of the same external dependency. That is why large Bazel codebases bias toward a one-version policy for third-party code: otherwise some top-level target eventually becomes the place where those requirements collide.2,4

Legacy WORKSPACE-based dependency management made these conflicts painful because transitive external dependencies had to be managed more manually. In Bzlmod, version resolution is collective: Bazel looks across the module graph and uses Minimal Version Selection to choose a reproducible version instead of drifting to whatever a package manager happened to fetch. The deeper mechanics belong to 3.1 Dependency Management, and the brownfield migration story belongs to M1 WORKSPACE → Bzlmod.2,5

Stale dependencies are actual-vs-declared mismatches

The most common day-to-day dependency issue is simpler: the code and the BUILD file disagree about what is direct. Bazel's rule is precise. The graph of actual dependencies must be a subgraph of the graph of declared dependencies, but the declared graph should not grow far beyond what the code really uses.1

If target A starts using symbols from C while its BUILD file still lists only B, the build may keep working for a while because B currently depends on C too. That is a fragile coincidence, not a valid direct-dependency declaration. The failure appears later, when B is refactored and the transitive path disappears. This is exactly why 2.1.3 Dependency Types emphasized "declare direct deps only": missing direct deps make builds brittle, and extra declared deps make the graph noisier than it needs to be.1,2

Re-exports hide the real edge

Re-exports are a specific way to create that fragility. If A gets a symbol from C through B, A is now coupled not just to B, but to part of B's transitive closure. That can increase rebuild surface, and it means A breaks the moment B stops forwarding C, even if A's own source code never changed.3

Sometimes that forwarding is intentional: B is acting as a stable facade. That curated pattern is different, and it shows up later in H.5.1 Shared Components. The problem here is the accidental shortcut, where A conceptually depends on C but relies on B to smuggle it in.3

key takeaway

Most dependency bugs come from inaccurate or fragile graph declarations. If the edge is real, declare it directly. If two paths want incompatible versions, solve that in dependency management instead of hiding the conflict with ad hoc BUILD-file tricks. If an intermediate target is re-exporting something you use, decide whether it is a real public facade or just hiding the dependency you should name yourself.1,3,4,5

Check your understanding · 3 questions

1.Two parts of a Bazel module graph need different versions of the same external library — a classic diamond conflict. Under Bzlmod, what happens by default?

Select one answer

2.Which of the following are valid ways to investigate a diamond dependency or hidden transitive path in Bazel?

Select all that apply

3.True or false: statements about dependency management and version conflicts.

Choose True or False for each sentence

Bzlmod's Minimal Version Selection resolves diamond version conflicts collectively across the module graph rather than per-package.
Re-exporting a symbol through an intermediate target is always safe because Bazel tracks transitive closure automatically.
0 of 3 answered

Footnotes

  1. Dependencies — actual vs declared dependencies, overapproximation rule, and the undeclared transitive dependency timeline 1 2 3 4

  2. Dependency Management — strict direct-dependency hygiene, one-version rule, and the pain of manual transitive external dependency management 1 2 3 4

  3. Managing dependency graph in a large codebase — diamond dependencies, stale dependency metadata, and re-exports as hidden coupling 1 2 3 4

  4. Google's One-Version Rule (Third-Party Dependencies) — why conflicting versions eventually collide in a shared dependency graph 1 2

  5. Bazel modules — diamond dependency resolution with Minimal Version Selection in Bzlmod 1 2

  6. Query guidesomepath() and allpaths() for tracing why a target depends on something

  7. The Bazel Query Referencesomepath() returns one arbitrary path, allpaths() returns all paths

  8. Query quickstart--noimplicit_deps and Graphviz output for cleaner dependency-path visualization