A property read from another subproject is sometimes correct and sometimes the default value across builds. How do you diagnose and fix it?
answer
- intermittent default = undefined order
- grep eager project(':...') reads
- beforeProject hook to log order
- config cache pinpoints the line
- fix: provider; fallback: evaluationDependsOn
basics
~10 sIt's almost certainly an evaluation-order bug: you're reading the other project eagerly at configuration time and it isn't always configured first. Replace the eager read with a lazy Provider, or force order with evaluationDependsOn.
solid answer
~50 sIntermittent correct-vs-default results are the classic fingerprint of **undefined configuration order**. Somewhere you read another project's state eagerly — `project(":lib").version`, an extension value, a property — at configuration time. When Gradle happens to evaluate `:lib` first you get the real value; when it evaluates your project first you capture the default. To diagnose: run with `--console=verbose`/`-i` or print evaluation order via a `gradle.beforeProject {}` hook, and search the script for eager cross-project reads. To fix: prefer making the value a **lazy `Provider`** wired into task inputs so it's resolved at execution (when all projects are configured) — this is the durable fix and is configuration-cache safe. If you truly must read the configured model at configuration time, add `evaluationDependsOn(":lib")` so `:lib` is always evaluated first. Don't 'fix' it by renaming projects to luck into a different order.
code
bash · 4 lines# Surface the illegal cross-project read precisely
./gradlew :app:bundle --configuration-cache -i
# Then grep for the eager reads to fix
grep -rn "project(\":" --include=build.gradle.kts | grep -v dependenciesgo deeper
Recognize the symptom points to ordering and that the value is read too early; escalate for the fix.
Identify the eager read and apply evaluationDependsOn or a provider.
Drive a provider-based fix, use the configuration cache to localize it, and reject lucky-order workarounds.
Make config-cache-on the default to catch this class of bug in CI and codify provider wiring in guidelines.
## The fingerprint A value that is *sometimes* right and *sometimes* the default — varying between machines, after a clean, or when you add/remove projects — is the textbook symptom of **non-deterministic configuration order**. Subproject evaluation order is undefined, so an eager cross-project read sees a configured value only when luck puts the producer first. ## Diagnose 1. **Find the eager read.** Grep the build for `project(":` outside `dependencies { }`, plus `.extensions.getByType`, `.version`, `.getByName`. These executed during configuration. 2. **Observe order.** Add a settings/init hook: ```kotlin // settings.gradle.kts or init script gradle.beforeProject { println("configuring ${'$'}{path}") } ``` Run twice and compare; if the producer sometimes prints after the consumer, that's your bug. 3. **Confirm with configuration cache.** `--configuration-cache` will often hard-fail on the exact illegal cross-project read, pointing at the line. ## Fix — preferred (lazy) Model the value as a `Provider` and wire it into the consuming task's input: ```kotlin val libVersion = providers.provider { project(":lib").version.toString() } tasks.register("emit") { val f = layout.buildDirectory.file("v.txt"); outputs.file(f) doLast { f.get().asFile.writeText(libVersion.get()) } // resolved at execution } ``` Now there is no configuration-time read at all; order is irrelevant and it's cache-safe. ## Fix — when you must read at config time ```kotlin evaluationDependsOn(":lib") // :lib always evaluated first val v = project(":lib").version ``` Use this only if you need `:lib`'s *configured model* during your configuration. Beware cycles and the configuration-time cost. ## What NOT to do Don't rename projects or reorder `include` to coax a 'good' order — it's not contractual and will regress. Don't sprinkle `evaluationDependsOn` everywhere; reach for providers first.
- Why does the configuration cache help pinpoint this bug?It forbids tasks/scripts from reading another project's mutable state and fails with the offending location, so it surfaces exactly the eager cross-project access causing the nondeterminism.
- Why is reordering include() the wrong fix?include order does not define configuration order; the 'good' order is luck and not contractual, so it will silently regress on another machine or after a structural change.
saying these in an interview costs you the question
- Fixing it by renaming projects or reordering include to get a lucky order.
- Adding evaluationDependsOn everywhere instead of using a provider.
- Assuming the default value means the producer is broken rather than evaluated late.