Skip to content

syncRoomSchemaSnapshot fails when release lint shares a Gradle invocation with the debug test tasks #1711

Closed

Description

@ryanbr

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-tasks

Confirmed 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

  1. ryanbr commented on Aug 28, 2026

    @ryanbr
    OwnerAuthor

    Handled across two PRs.

    #1712 — the ordering itself. room.schemaLocation is set once outside any variant block, so every variant's KSP round writes that directory, while syncRoomSchemaSnapshot declared a relationship with only some of its producers. The one-variant dependsOn stays, 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 not kspFullReleaseKotlin, which writes the same directory and is normally absent from the graph. Broadened to every ksp*Kotlin round.

    #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 mustRunAfter that silently forces extra work is the regression worth fearing here:

    • the three-task command from this issue now succeeds, and still fails on the commit before build: order the Room schema snapshot after every KSP round, not just the test ones #1712
    • it runs the debug, unit-test and release rounds exactly once each
    • a plain testFullDebugUnitTest still runs only the debug and unit-test rounds — no release round — so the one-variant optimisation is untouched
    • 4,725 tests, 0 failures, and SchemaOracleTest 4/4, which is the test that fails when the export goes missing

    Worth 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-test passes with or without the fix. The evidence is local, and the checks above were run before and after each change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions