Hi @aurelien.coet !
Thanks for looking into this so quickly!
Since I cannot share our proprietary repository, I spent some time reverse-engineering our app/build.gradle.kts to figure out exactly why it happens in our project but not in a blank MRE.
The aggressive task graph optimization in AGP 9.2 seems to only expose this Provider API violation when the project uses a combination of multiple Product Flavors, Build Types, and resValue injections.
Here is the structural outline of our app/build.gradle.kts that causes the sonarResolver to crash when we run ./gradlew sonar:
Kotlin
android {
defaultConfig {
// We inject multiple resources at the default config level
resValue("string", "default_key", "xxx")
}
buildFeatures {
resValues = true
}
buildTypes {
debug { ... }
release { ... }
}
flavorDimensions += "default"
productFlavors {
create("staging") {
dimension = "default"
// We inject variant-specific resources here
resValue("string", "variant_key", "yyy")
}
create("production") {
dimension = "default"
}
}
}
Our CI Pipeline Context: For context on how this happens in the real world, we separate our testing and analysis into different steps in our GitHub Actions pipeline, which is a standard CI pattern:
YAML
- name: Run unit tests with Jacoco code coverage
run: ./gradlew jacocoTestReport -PexcludeReleaseTests --stacktrace
- name: Run SonarCloud
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: "https://sonarcloud.io"
run: |
./gradlew sonar -x :app:sonarResolver --info --stacktrace
Because ./gradlew sonar is executed in a completely separate Gradle invocation, tasks like :app:generateStagingDebugResValues are intentionally excluded from the Gradle execution graph.
When SonarResolverTask runs, it asks AGP for the generated resource paths for all variants. Because resValue was heavily used across flavors, AGP provides these paths wrapped in a lazy flatmap(provider(...)).
The Crash: When the Sonar plugin attempts to read these paths, it forces the evaluation of that lazy provider. Gradle 9 enforces strict Provider API rules—even if the files were successfully generated and exist on disk from the previous jacocoTestReport step, Gradle immediately throws an InvalidUserCodeException because Sonar is querying the mapped value of a task that has not completed and is not scheduled to run in the current execution graph.
Here is the exact sanitized stack trace from our logs pointing to the Sonar plugin classes causing the eager resolution:
Plaintext
Execution failed for task ':app:sonarResolver' (registered by plugin 'org.sonarqube').
> Querying the mapped value of list(interface java.util.Collection, item(...) + item(list(interface org.gradle.api.file.Directory, item(property(org.gradle.api.file.Directory, property(org.gradle.api.file.Directory, property(org.gradle.api.file.Directory, map(org.gradle.api.file.Directory flatmap(provider(task 'generateStagingDebugResValues', class com.android.build.gradle.tasks.GenerateResValues)) check-type())))))))) before task ':app:generateStagingDebugResValues' has completed is not supported
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:sonarResolver' (registered by plugin 'org.sonarqube').
... [Gradle execution trace] ...
Caused by: org.gradle.api.InvalidUserCodeException: Querying the mapped value of list(...) before task ':app:generateStagingDebugResValues' has completed is not supported
at org.gradle.api.internal.provider.TransformBackedProvider.lambda$beforeRead$0(TransformBackedProvider.java:98)
at org.gradle.api.internal.provider.TransformBackedProvider.beforeRead(TransformBackedProvider.java:95)
at org.gradle.api.internal.provider.TransformBackedProvider.calculateOwnValue(TransformBackedProvider.java:80)
at org.gradle.api.internal.file.AbstractFileCollection.iterator(AbstractFileCollection.java:165)
at org.sonarqube.gradle.SonarUtils.exists(SonarUtils.java:229)
at org.sonarqube.gradle.SonarResolverTask.getAbsolutePaths(SonarResolverTask.java:165)
at org.sonarqube.gradle.SonarResolverTask.run(SonarResolverTask.java:188)
The Business Impact: As you can see in the file “project_tree.png”, because the :app:sonarResolver task fails, the Sonar scanner is completely skipping our app module. While our core and feature modules successfully report tens of thousands of lines, the app module shows a line count of “—” (in our case 94k lines of code). This issue originates at SonarUtils.java:229 being called by SonarResolverTask.java:165, which attempts to check .exists() on an unresolved Directory Property, effectively blinding us to our main application module on the SonarCloud dashboard.
The plugin needs to gracefully handle or ignore these lazy DirectoryProperty collections when their producer tasks are absent from the graph, rather than eagerly iterating them.
I hope this helps the engineering team pinpoint the fix!