skip to content

A task you expected to be FROM-CACHE shows up as SUCCESS (a cache miss) in the Build Scan. How do you use the scan to diagnose why it missed?

level: seniorimportance: should knowfreq 45%

answer

  1. cacheable vs cacheable-but-missed
  2. diff two scans' input hashes
  3. absolute paths break key
  4. PathSensitivity.RELATIVE
  5. volatile inputs / toolchain drift

basics

~10 s

Open the task in the scan, check its outcome and cacheability, and compare its cache key/inputs against a previous build's scan. A changed input hash or a non-portable absolute path usually explains the miss.

solid answer

~50 s

First confirm the task is actually **cacheable** — the scan's task detail and the Performance cache view tell you if it was marked `@CacheableTask` and produced a cache key at all; non-cacheable tasks always 'miss'. If it is cacheable but missed, the cause is a **different cache key** than the prior build. The scan exposes the task's **inputs** and their hashes; comparing two scans (the cached run vs the missing run) reveals which input changed. Common culprits: absolute paths leaking into inputs (machine-specific), system properties or environment variables captured as inputs, a changed compiler/Gradle/JVM version normalized into the key, timestamps in generated files, or line-ending/charset differences. The scan's cache section also shows hit rate and whether the remote cache was even reachable. Develocity's **build comparison** feature diffs two scans' inputs directly, which is the fastest path to the diverging input.

code

kotlin · 12 lines
kotlin
@CacheableTask
abstract class GenerateTask : DefaultTask() {
    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val sources: ConfigurableFileCollection

    @get:Input
    abstract val version: Property<String>

    @get:OutputDirectory
    abstract val outputDir: DirectoryProperty
}

go deeper

for a junior

Know that a cache miss means the inputs changed since the cached run; identifying which is harder.

for a middle

Check cacheability first, then look at the task's inputs in the scan for an obvious changed value.

for a senior

Systematically diff two scans, recognize relocatability pitfalls (paths, timestamps, toolchain), and fix with normalization annotations.

for a principal

Drive a relocatability/cacheability standard across the org and monitor hit-rate regressions via Develocity trends.

## Why a cacheable task misses Gradle's build cache keys a task by a **hash of all its inputs**: the task's input properties, the contents of its input files (normalized), its classpath, the task implementation/action classes, and relevant Gradle/JVM identifiers. If *any* of these differs from the run that produced the cache entry, the key differs and you get a **miss** — the task executes (`SUCCESS`) instead of restoring (`FROM-CACHE`). ## Step 1 — is it even cacheable? In the scan, open the task. Built-in tasks like `compileJava`/`test` are annotated `@CacheableTask`; many custom and third-party tasks are **not**. A task with no declared outputs, or one that opts out via `outputs.cacheIf { false }`, will never produce a cache entry. The scan's task detail and the Performance cache breakdown distinguish *not cacheable* from *cacheable-but-missed*. ## Step 2 — compare two scans Take the scan from a build where the task *was* `FROM-CACHE` and the scan where it *missed*. The task's **inputs** section lists each input property and a content hash. Diff them. Develocity offers a **build scan comparison** that automates this, highlighting the exact input whose hash changed. ## Common root causes - **Absolute paths as inputs** — a path under `/Users/alice/...` vs `/home/ci/...`. Fix with proper path-sensitivity normalization (`@PathSensitive(RELATIVE)`), or relocatable inputs. - **Volatile inputs** — timestamps, build numbers, or `System.currentTimeMillis()` baked into generated sources. - **Environment/system properties** captured as task inputs that differ between dev and CI. - **Toolchain drift** — different JDK or compiler version; these are part of the key. - **Normalization mismatch** — line endings (CRLF vs LF) or file ordering not normalized. ## Step 3 — fix and verify After declaring inputs relocatably (normalization, relative path sensitivity, filtering volatile properties), run twice on two machines and confirm the second scan shows `FROM-CACHE`. ```kotlin @CacheableTask abstract class GenerateTask : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) // path-relocatable key abstract val sources: ConfigurableFileCollection @get:Input // avoid absolute paths / timestamps here abstract val version: Property<String> @get:OutputDirectory abstract val outputDir: DirectoryProperty } ```

  • Why do absolute input paths cause cache misses across machines?
    Without RELATIVE path sensitivity the absolute path becomes part of the input fingerprint, so the key differs between machines whose checkouts live in different directories — every cross-machine restore misses.
  • How would you confirm a remote cache miss isn't actually a connectivity problem?
    The scan's build cache section reports whether the remote cache was reachable and shows load/store activity; a missing remote node or auth failure shows as no remote attempts rather than a key mismatch.
  • What's the fastest scan-native way to find the diverging input?
    Use Develocity's build-scan comparison to diff the two builds' task inputs; it highlights the property whose content hash changed.

saying these in an interview costs you the question

  • Assuming any SUCCESS task 'should' have been cached without first checking it's @CacheableTask.
  • Blaming the cache backend when the real cause is a non-relocatable input (absolute path / timestamp).
  • Forgetting that the task implementation classpath and JDK version are part of the cache key.

context