A consumer task reads a generated file but the producer doesn't run first (stale or missing file). How do you diagnose and fix a broken inferred dependency?
answer
- works on 2nd run = missing inferred edge
- clean consume to expose it
- causes: .get(), File, un-annotated, wrong source
- fix = restore flatMap wiring + annotate
- dependsOn is a smell here
basics
~20 sUsually the provider link was severed — someone called .get() while configuring, used a plain File, or left the input un-annotated. Restore lazy provider wiring (map/flatMap on the producer's output property) and the dependency re-infers; avoid patching with dependsOn.
solid answer
~50 sSymptoms — 'file not found', stale output, or correct only on a clean second run — usually mean Gradle never learned the producer must run first because the provider chain was broken. Common causes: (1) `.get()`/`.orNull` called during configuration, extracting a plain value that lost the producer; (2) a hardcoded `File`/`String` path that isn't task-aware; (3) the consumer's input field isn't annotated (`@InputFile` etc.), so Gradle doesn't scan it; (4) wiring the wrong thing (a literal `layout.buildDirectory.file(...)` instead of the *producer task's* output property). Diagnose by running with `--info`/`--console=verbose` and inspecting the task graph (e.g. a build scan or `:consumer` execution order), and check whether the input property holds a producer-backed provider. Fix by re-establishing lazy wiring: `consumer.input.set(producer.flatMap { it.out })`. Use explicit `dependsOn` only as a last resort for pure lifecycle tasks, not to paper over a severed provider.
code
bash · 4 lines# Reproduce: a clean run exposes the missing producer dependency
./gradlew clean consume --info
# If this fails but `./gradlew consume` (warm) passes,
# the inferred dependency is broken -> restore provider wiring.go deeper
Recognize 'works second time' as a dependency problem and know to test with clean.
List the usual causes (.get(), File path, un-annotated input) and fix by restoring flatMap wiring.
Drive a diagnosis with clean + --info + build scan, distinguish data-flow vs ordering, and explain why dependsOn is the wrong patch.
Institute prevention: ban config-time .get(), require annotated provider-wired I/O, and add clean-based CI gates to catch severed edges before they ship.
## The failure pattern Inferred dependencies fail *silently*: there's no error at configuration time, just a runtime 'file not found' or a stale file. A classic tell is **'it works on the second run'** — the producer happened to run in an earlier invocation, leaving the file on disk. ## Root causes (in rough order of frequency) 1. **Eager resolution.** `producer.get().out.get()` or `someProvider.get()` during configuration returns a plain value, discarding the producer metadata. The single most common cause. 2. **Non-task-aware value.** Wiring a `File`, `String`, or a fresh `layout.buildDirectory.file("x")` literal — none of these know about the producer task. 3. **Un-annotated input.** Gradle only scans properties carrying input annotations. If the field holding the provider has no `@InputFile`/`@InputFiles`/`@Nested`, it's never walked, so no edge is added. 4. **Wrong source.** Consuming a *copy* of the path rather than the producer task's actual output property. 5. **Custom plugin not declaring outputs** so `outputs.files` is empty and built-by is meaningless. ## Diagnosis workflow ```bash # See actual execution order and skip/UP-TO-DATE reasons ./gradlew consume --info # Force-clean to expose the missing edge (no leftover file) ./gradlew clean consume ``` - If `clean consume` fails but a warm run passes → almost certainly a missing inferred dependency. - A **build scan** shows the task's inputs and which provider/task backs each; a missing producer there confirms a severed link. - Check the code: does the consumer's input come from `producer.flatMap { it.output }`, and is it annotated? ## The fix ```kotlin // before (broken): eager + non-task-aware consumer.src.set(producer.get().out.get()) // after (inferred): lazy provider chain consumer.src.set(producer.flatMap { it.out }) ``` Ensure the consumer's property is annotated: ```kotlin @get:InputFile abstract val src: RegularFileProperty ``` ## Why not just add dependsOn? `dependsOn(producer)` fixes the *ordering* but not the *data*: you'd still pass a stale or wrong path, and you reintroduce the coupling bug for the next refactor. Treat `dependsOn` as a smell here — the right fix restores the provider wiring so value and ordering travel together. Reserve `dependsOn` for genuine lifecycle aggregators with no data flow. ## Prevention - Never call `.get()`/`.orNull` while configuring. - Always derive outputs from `layout.buildDirectory` and consume the producer's output property. - Annotate every input/output. - Add a `clean`-based CI check so missing edges fail fast.
- Why is 'it only fails on a clean checkout / clean task' such a strong signal?Because a warm build dir still has the producer's output on disk from a prior run, masking the missing edge. clean removes it, so the absent dependency surfaces immediately.
- Is adding dependsOn an acceptable fix?Only as a last resort. It restores ordering but not the value flow, so you may still consume a stale/wrong path and you reintroduce the decoupling bug. Prefer restoring the lazy provider wiring.
- How does a build scan help here?It lists each task's inputs and which task/provider produces them, so a missing producer for an input pinpoints exactly where the provider chain was severed.
saying these in an interview costs you the question
- Reaching for dependsOn as the first fix instead of repairing the provider chain.
- Blaming caching/up-to-date checks when the real issue is a missing edge.
- Not testing with clean, so the bug stays hidden behind warm output.