"app:sonarResolver" crashes in AGP 9.2+ due to early Provider API resolution

When attempting to run SonarQube analysis on an Android project using AGP 9.2.1 and Sonar Scanner 7.3.1.8318, the :app:sonarResolver task crashes Gradle due to strict Provider API violations.

If the analysis is run without executing all Android generation tasks in the same invocation, Sonar forces the evaluation of lazy DirectoryProperty outputs before their producer tasks have run.

Steps to Reproduce:

  1. Clean the project or run a CI pipeline that pre-compiles binaries in a separate step.

  2. Run ./gradlew sonar

  3. The build crashes immediately during task graph execution.

Error Log Snippet:

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 'generateStagingDebugAndroidTestResValues', class com.android.build.gradle.tasks.GenerateResValues)) check-type())))))))) before task ':app:generateStagingDebugAndroidTestResValues' has completed is not supported

Workaround Attempted: Previously, we bypassed this by using -x :app:sonarResolver to skip the app module entirely, but we want our app module analyzed. Attempting to hardcode sonar.sources and removing sonar.android.variant did not stop the plugin from querying the AGP variant graph.

Environment:

  • Gradle Version: 9.5.1

  • AGP Version: 9.2.1

  • Sonar Scanner Version: 7.3.1.8318

  • Kotlin Version: 2.4.0

Expected Behavior: The Sonar plugin should gracefully handle unresolved lazy properties in AGP 9.x without triggering a Provider API crash, or allow a flag to completely bypass AGP graph querying in favor of hardcoded paths.

Hello @Gustavo_Morales,

Thank you for the quick feedback !

Unfortunately, we didn’t encounter this issue during the validation of the plugin and I cannot seem to reproduce it. Could you share more information about your specific configuration ? Do you use any specific plugins or tasks that generate sources in your build ? I see that the :app:generateStagingDebugAndroidTestResValues task appears to cause the issue, but I cannot reproduce the crash you observe locally.

Any additional information could help us to identify the specific cause for the failure.

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!