A teammate reports the configuration cache never hits — every run prints 'Calculating task graph'. How would you diagnose whether it's a serialization failure or an input-invalidation issue?
answer
- run identical command twice: store then load
- problems reported → serialization/unsupported API
- no problems but always misses → tracked input changes
- open the HTML problems report
- bisect variable inputs (env/timestamps/regenerated files)
basics
~20 sRun twice with the identical command. If the store run reports configuration cache problems (e.g. unserializable state), it can't be reused — fix those. If there are no problems but it still misses, a tracked input (a property, env var, or file) is changing between runs.
solid answer
~50 sReproduce by running the exact same invocation twice. Two distinct root causes look alike from the console: 1. **Serialization failure** — the store run logs configuration cache *problems* such as 'cannot serialize object of type ...' or 'invocation of Task.project at execution time'. Gradle may store a degraded entry or refuse reuse. Fix by removing eager captures: read values into serializable locals via `Provider`/`Property`, declare task `inputs`/`outputs`, and avoid capturing `Project`/`Configuration` in actions. 2. **Input invalidation** — no problems are reported, yet every run misses because a tracked input changes each time: a timestamp/random read through a provider, an env var that differs (e.g. a per-run `BUILD_ID`), or a file rewritten before each build. Identify it by comparing the inputs the entry recorded against the environment, or by removing variable inputs and confirming the hit returns. The HTML problems report and verbose configuration-cache logging point you to the exact offending property, type, or undeclared read.
code
bash · 8 lines# Reproduce: same command twice
$ ./gradlew check # run 1 -> store
$ ./gradlew check # run 2 -> should be a hit
# Still 'Calculating task graph' on run 2? Inspect problems:
$ ./gradlew check --configuration-cache
# -> follow the printed HTML report path, e.g.
# file:///.../build/reports/configuration-cache/<hash>/configuration-cache-report.htmlgo deeper
Know to run the command twice and look for whether problems were reported.
Separate the two failure modes and apply the right fix (serializable capture vs stabilizing inputs).
Drive triage methodically with the problems report and input bisection; explain environment-divergence misses.
Establish team practices: treat config-cache problems as build errors in CI, lint for undeclared reads, and standardize on stable, tracked inputs.
## Step 1 — reproduce deterministically Run the **same** command twice in a row with nothing else changing. A correct setup misses on run 1 (store) and hits on run 2 (load). Persistent misses on run 2+ mean either the entry isn't reusable (serialization problems) or its key keeps changing (input invalidation). ## Step 2 — read the problems output On the **store** run, watch for lines like: - `Configuration cache problems found in this build.` - `cannot serialize object of type 'org.gradle.api.Project' ...` - `invocation of 'Task.project' at execution time is unsupported` - `read system property 'x' / environment variable 'Y'` (undeclared read) These indicate a **serialization / unsupported-API** issue: the configured state can't round-trip, so reuse is blocked or degraded. The fix is structural — capture serializable values during configuration (see the lazy-provider pattern), declare inputs/outputs, and inject services rather than capturing them. ## Step 3 — if there are no problems, it's input invalidation When run 2 still misses but **no problems** are reported, a **tracked input changed**. Common culprits: - An env var that varies per run (`BUILD_NUMBER`, `RANDOM`, a CI run id) read via `providers.environmentVariable`. - A system property toggled by a wrapper script. - A file read at configuration whose content is regenerated each run (e.g. a generated `version.txt` or properties file). - Reading `System.currentTimeMillis()`/`UUID.randomUUID()` into something captured as a config input. Bisect by temporarily removing or fixing the suspect input and re-running; when run 2 hits, you've found it. ## Step 4 — use the tooling - The build prints a path to an **HTML problems report** summarizing each problem with the responsible location/type — open it to jump straight to the offending code. - Increasing log verbosity surfaces which inputs were recorded. ## Mental model for triage | Symptom on run 2+ | Likely cause | Fix | |---|---|---| | Problems reported, miss/degraded reuse | Unserializable state / unsupported API | Capture serializable values, declare inputs, inject services | | No problems, always miss | Tracked input changes each run | Make the input stable or stop reading it at config time | | Hits locally, misses on CI | Empty cache dir on fresh agent | Persist/warm `.gradle/configuration-cache/` | ## Example fix for a serialization problem ```kotlin // Before: undeclared read + Project capture -> problems, no clean reuse tasks.register("report") { doLast { println("${project.name} built at ${System.getenv("BUILD_ID")}") } } // After: tracked input + serializable locals -> clean store, then hits val name = project.name val buildId = providers.environmentVariable("BUILD_ID").orElse("local") tasks.register("report") { val n = name; val b = buildId doLast { println("$n built at ${b.get()}") } } ``` Note: if `BUILD_ID` genuinely changes every run, even the tracked version will keep invalidating — which correctly diagnoses it as an *input* problem, not a serialization one.
- Two engineers see different behavior: one hits, one always misses with the same code. What's a likely cause?An environment difference — a tracked env var or system property that one shell sets and the other doesn't, or a per-run value like a build id. The cache key includes those tracked inputs, so divergent environments produce divergent keys.
- The store run reports 'invocation of Task.project at execution time'. What does that tell you?An action accesses the project model at execution time, which is unsupported under the configuration cache because no live model exists on a hit. Capture the needed value during configuration into a serializable local instead.
saying these in an interview costs you the question
- Concluding 'configuration cache is broken' instead of distinguishing serialization vs input invalidation.
- Ignoring the HTML problems report that names the exact offending location.
- Trying to fix serialization when the real issue is a per-run changing input (or vice versa).