Repository navigation
syncRoomSchemaSnapshot fails when release lint shares a Gradle invocation with the debug test tasks #1711
Description
Hit while verifying #1710. Not caused by that change — it reproduces on unmodified main.
Symptom
Running release lint in the same Gradle invocation as the debug test tasks fails:
> Task :app:syncRoomSchemaSnapshot FAILED
A problem was found with the configuration of task ':app:syncRoomSchemaSnapshot' (type 'Sync').
- Gradle detected a problem with the following location:
'.../android/app/build/generated/roomSchemas'
Reason: Task ':app:syncRoomSchemaSnapshot' uses this output of task ':app:kspFullReleaseKotlin'
without declaring an explicit or implicit dependency.
Reproduce
cd android
./gradlew -p . compileFullDebugKotlin testFullDebugUnitTest lintVitalFullRelease \
-PstagingRelease --no-daemon --no-build-cache --rerun-tasksConfirmed on unmodified main in an isolated git worktree, so it is not branch-specific. The unit tests themselves pass (tests=4725 failures=0) — the failure is the later task.
Cause
app/build.gradle.kts:160-162 sets the schema location once, outside any variant block:
val roomSchemaDir = layout.buildDirectory.dir("generated/roomSchemas")
ksp { arg("room.schemaLocation", roomSchemaDir.get().asFile.absolutePath) }So every variant's KSP task writes that directory — kspFullDebugKotlin, kspFullReleaseKotlin, the unit-test ones, all of them.
But syncRoomSchemaSnapshot (:203-213) reads it while declaring a relationship with only some of its producers:
dependsOn(tasks.matching { it.name == "kspFullDebugKotlin" })
mustRunAfter(tasks.matching { it.name.startsWith("ksp") && it.name.contains("UnitTest") })dependsOn is deliberately one variant — the comment explains why, and that reasoning is sound: the schema derives from the entities alone, so every variant's KSP writes byte-identical JSON, and depending on all four would make one testFullDebugUnitTest run four KSP rounds.
The gap is the ordering line. It guards against unit-test KSP clearing the snapshot after Sync copied it, but kspFullReleaseKotlin writes the very same directory and is not covered. It is normally absent from the graph, which is why this has never surfaced — CI evidently runs lint in its own invocation.
Suggested shape
Broaden the existing mustRunAfter to every KSP task that writes the directory, rather than only the unit-test ones — the hazard its comment describes is identical for the release round:
mustRunAfter(tasks.matching { it.name.startsWith("ksp") && it.name.endsWith("Kotlin") })Ordering-only, so it forces nothing to execute and the one-variant dependsOn optimisation is preserved. I have not tested this, so treat it as the shape rather than the patch.
Why it is worth fixing
Nothing is broken in shipped builds, and CI is unaffected. The cost is local: the natural "build, test and lint in one go" command fails with an error that reads like a Room/schema problem, which sends you looking in the wrong place. I lost a cycle to exactly that before checking it against clean main.
Activity
build: order the Room schema snapshot after every KSP round, not just…
ryanbr commentedon Aug 28, 2026
Handled across two PRs.
#1712 — the ordering itself.
room.schemaLocationis set once outside any variant block, so every variant's KSP round writes that directory, whilesyncRoomSchemaSnapshotdeclared a relationship with only some of its producers. The one-variantdependsOnstays, deliberately: the schema derives from the entities alone, so depending on all of them would cost four KSP rounds per test run. The gap was the ordering line, which covered the hazard actually observed — unit-test KSP clearing the snapshot after Sync copied it — but notkspFullReleaseKotlin, which writes the same directory and is normally absent from the graph. Broadened to everyksp*Kotlinround.#1714 — the readability half. Writing #1712 meant adding a matcher spelled almost identically to one a few lines above it, and I did not notice that the existing one deliberately inverts on the UnitTest/AndroidTest rounds. Two near-identical predicates meaning opposite things is a trap for whoever reads them next, so the shared half is now named and its comment states the opposition rather than leaving it to be diffed by eye.
Verified rather than reasoned, since a
mustRunAfterthat silently forces extra work is the regression worth fearing here:testFullDebugUnitTeststill runs only the debug and unit-test rounds — no release round — so the one-variant optimisation is untouchedSchemaOracleTest4/4, which is the test that fails when the export goes missingWorth recording for whoever hits something like this next: CI cannot catch a regression here. It invokes lint and the test tasks separately, which is exactly why this survived —
build-and-testpasses with or without the fix. The evidence is local, and the checks above were run before and after each change.