TL;DR: Your Bazel dashboard says 85% cache hit rate. Your builds are still slow. Something doesn't add up. The problem is that aggregate cache hit rate is one of the most misleading metrics in the build tooling world. It looks like signal. It's mostly noise. The only way to actually fix cache efficiency is to move past the aggregate number and look at what's happening at the action level — which specific actions are missing, why, and what their blast radius is in your dependency graph.
The Number Doesn't Tell You What's Missing
When engineers report cache hit rate, they're usually reporting the ratio of cache hits to total action executions. That sounds reasonable until you think about what it actually captures.
A monorepo with 50,000 actions where 42,500 hit cache is "85% hit rate." But if the 7,500 misses are concentrated in your core libraries — the ones everything else depends on — you've just invalidated half your dependency graph. Every downstream action re-executes. Your build time triples. Your dashboard still shows 85%.
The aggregate hides the blast radius.
Worse, most teams look at hit rate averaged over time, across all targets, across all users. That smoothing buries the real patterns. An engineer on a feature branch might have a 40% hit rate because their environment is subtly different from CI. You'll never see it in the aggregate.
The Real Root Causes of Cache Misses
Cache misses aren't random. They cluster around a handful of root causes, and each one requires a different fix.
Environment drift is the silent killer. Bazel's cache key includes environment variables that get folded into action inputs. If a developer has JAVA_HOME set differently than CI, or a slightly different PATH, actions that should hit cache don't. This is especially brutal in teams with heterogeneous dev machines — macOS laptops, Linux workstations, remote dev boxes, all with slightly different toolchain setups.
Platform mismatches are a related but distinct problem. If you're running remote execution and your platform spec doesn't exactly match what was used to populate the cache, you miss. A container image hash that changed, an OS constraint that shifted, an execution property that got added — any of these will cause cold misses that look identical to legitimate rebuilds at the aggregate level.
Input changes are expected, but their scope often isn't. When a header file changes, Bazel correctly invalidates all downstream actions. The question is whether that invalidation is accurate. Header maps, generated files, and symlinked sources can all cause inputs to change when the underlying logic hasn't. If your generated protobuf files include a timestamp, you're burning your cache on every build.
Command changes are the sneakiest. Bazel encodes the full command line into the cache key. Flags added to .bazelrc by different engineers, wrapper scripts that inject arguments, or toolchain rules that compute flags dynamically — all of these can cause actions to miss cache silently. No error, no warning, just a miss.
Why You Need Action-Level Visibility
The only way to actually fix cache efficiency is to look at individual actions, not aggregate rates.
You need to know: which specific actions are missing? What were their cache keys? What changed between the hit and the miss — was it an input file, a command flag, an environment variable? Which targets in the dependency graph are responsible for the most downstream misses?
Without that, you're guessing. You can tune .bazelrc flags, standardize toolchains, and tighten your platform specs all day — but if you can't observe the actual miss pattern, you're flying blind.
This is different from what most teams have. Most teams have build event logs, maybe streamed to a BES endpoint, with aggregate dashboards slapped on top. That gets you the 85% number. It doesn't get you the root cause.
What you actually need is per-action telemetry: action inputs, cache keys, miss reasons, dependency paths, execution durations — all queryable and correlated so you can identify the 200 actions causing 80% of your re-execution cost.
What This Actually Looks Like in Practice
When you have action-level visibility, the workflow changes completely. Instead of "our cache hit rate dropped this week, let's investigate," you get "these 12 actions in //infra/toolchain are missing because of an environment variable injected by our CI wrapper — here's the fix."
That's the difference between a metric and a diagnosis.
Consider the before and after. Without action-level visibility, the debugging cycle looks like this: someone notices builds are slow, an engineer spends half a day scanning execution logs, finds one suspicious pattern, pushes a fix, and waits to see if the aggregate number moves. Maybe it does. Maybe a different problem masked the improvement. Either way, the cycle repeats next month.
With action-level visibility, you can see the exact breakdown of cache misses by category — input changed, command changed, environment changed, platform changed, never cached, cache evicted. You can filter by target, by mnemonic, by user, by branch. You can compare two builds side-by-side and see exactly which actions diverged and why. The investigation that took half a day now takes five minutes.
This is what we built at Hermetiq — because we kept hitting the same wall ourselves. Hermetiq ingests Build Event Protocol data from every build, correlates it with remote cache telemetry, and provides per-action miss analysis with automatic categorization. When your cache hit rate drops, you don't have to guess why. You can see the specific actions, the specific miss reasons, and the specific changes that caused them.
The teams that have the best cache performance aren't the ones with the cleverest .bazelrc configs. They're the ones that can see what's actually happening inside their builds — and fix problems before they compound.
Stop trusting the aggregate. Hermetiq gives you per-action cache miss analysis with automatic categorization, trend tracking, and root cause attribution — so you can fix cache problems in minutes, not days. See how it works.
Related Articles
- Why Your Bazel Cache Hit Rate Is Terrible (And How to Fix It) — The deep dive into the six systemic root causes of poor cache hit rates and the concrete fixes for each one.
- How to Debug Bazel Cache Misses — A step-by-step guide to isolating and fixing individual cache misses using execution logs and automated tooling.
- Bazel Build Cost Visibility — Cache misses aren't just a performance problem — they're a cost problem. Learn how to track what your builds actually cost.