We’re experiencing a build failure in Bitrise when running Android test and build steps separately. The issue does not occur locally and appears to be related to how Bitrise caches KSP (Kotlin Symbol Processing) generated code between workflow steps.
We had in the past our own android library that used to generate code of classes annotated with a specific, we migrated the library to be in our main project, eventually the annotation path changed from x.y.z to a.b.c.
The builds will always fail now with Unresolved reference error, but with a weird behavior the first step always works the second always fails ( Android Test <–> Android Build ), the first one always works the second fails with unresolved reference.
Reproduction:
-
Test step runs first → Build step second: Test succeeds, but build fails with “Unresolved reference” errors
-
Build step runs first → Test step second: Build succeeds, but test fails with “Unresolved reference” errors
We tried another way, we change the annotation path from a.b.c → x.y.z and it doesn’t show Unresolved reference error anymore and it is finding the annotations! even though x.y.z never exists in the code base at all anymore, somewhere this is getting cached.
We removed all cache steps, wrote script to remove cache forcefully, nothing works but locally the build works and if we put android build before the test, the build is generated.
Hi @Hassan.Badran
Can you please contact our support via the support bubble, and send us the URLs of the builds there?
A bit of ideation, but w/o the build URLs it’s really hard to tell:
This really looks like stale KSP-generated sources / incremental state leaking between the two Bitrise steps.
What’s happening is: Bitrise runs two separate Gradle invocations in the same checkout (Android Test then Android Build, or the reverse). The first invocation generates (or reuses) KSP outputs for the variants it compiles. The second invocation then compiles a different set of variants/tasks and ends up seeing a mismatched mix of generated sources and/or KSP incremental caches—so it fails with Unresolved reference. Flipping the order flips which set of generated outputs is “left behind”, so the failure flips too.
The “we changed annotation path back to x.y.z and it works even though it doesn’t exist anymore” is another strong signal that old generated code (or cached compilation outputs) referencing the old FQCN is still being picked up somewhere under build/ (commonly build/generated/ksp/... and build/kspCaches/...) or via Gradle build cache/incremental compilation.
Why this shows up on Bitrise but not locally
Locally you typically run tasks in one session, or you have different cache state. On CI, you get a very reproducible sequence of two clean-ish invocations where the first produces outputs and the second reuses them incorrectly.
What to do
A few options, in order of practicality:
-
Run tests + build in a single Gradle invocation (probably works):
- e.g.
./gradlew <testTasks> <assembleTasks>
This avoids the “two invocations with stale generated outputs between them” problem entirely.
-
Force cleanup of KSP outputs between steps (more targeted than clean):
Delete per-module:
**/build/generated/ksp
**/build/kspCaches (and sometimes **/build/ksp)
Optionally also **/build/kotlin if Kotlin incremental compilation is involved.
-
As a diagnostic, make the second step run clean
If adding ./gradlew clean before the second step makes it go away, it confirms stale build outputs are the root cause. Then you can replace it with the more targeted KSP directory cleanup above.
-
Temporarily disable incremental/caching on CI to confirm
For example on CI only:
ksp.incremental=false
org.gradle.caching=false
- (optionally)
kotlin.incremental=false
If that fixes it, it’s definitely an incremental/cache invalidation issue.
-
Run the 2 steps in 2 separate builds, in parallel, e.g. via Bitrise Pipelines.
This way the 2 steps/Gradle commands won’t affect each other.
Extra thing to check
Make sure your processor is actually applied for all variants you compile (debug vs androidTest vs release). It’s common that test triggers debugAndroidTest tasks and build triggers release/assemble, and KSP outputs are variant-specific—so one step may not regenerate what the other needs.
If you share the build URLs with our support, we can point to the exact variant mismatch and recommend the cleanest/best solution.