Skip to content

ci: add trusted Gradle cache policy [WPB-27819] - #5137

Open
MohamadJaara wants to merge 5 commits into
developfrom
mo/ci/trusted-gradle-cache
Open

ci: add trusted Gradle cache policy [WPB-27819]#5137
MohamadJaara wants to merge 5 commits into
developfrom
mo/ci/trusted-gradle-cache

Conversation

@MohamadJaara

@MohamadJaara MohamadJaara commented Aug 5, 2026

Copy link
Copy Markdown
Member

https://wearezeta.atlassian.net/browse/WPB-27819

Goal

Reduce CI cache restore time and prevent untrusted or release workflows from reading or writing shared Gradle state.

What changed

  • Replaced the monolithic Warp Gradle cache with gradle/actions/setup-gradle.
  • Added one reusable Java and Gradle setup action for cache policy, wrapper validation, and optional KVM setup.
  • Enabled Gradle task-output caching only for the trusted develop lifecycle.
  • Isolated Gradle homes on long-lived self-hosted release-generation and QA runners.
  • Kept the Android UI-test AVD cache separate.

Cache trust model

Context Restore Write
Push to develop Yes Yes
Same-repository PR targeting develop Yes PR scope only
Merge queue targeting develop Yes No
Fork PR No No
Main/release push or release publication No No
Manual or scheduled generation No No

Impact

Develop and trusted PR builds can reuse smaller, Gradle-aware cache segments without repeatedly restoring the previous 8+ GB archive. Release, generation, fork, and unrelated branch workflows do not consume shared Gradle cache state.

Validation

  • All changed workflow and action YAML parses successfully.
  • The new composite action passes the repository's pedantic Zizmor policy.
  • Remaining cache references were audited; only the intentional AVD cache remains on Warp caching.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Test Results

2 240 tests  ±0   2 240 ✅ ±0   2m 6s ⏱️ -12s
  245 suites ±0       0 💤 ±0 
  245 files   ±0       0 ❌ ±0 

Results for commit b4c05f7. ± Comparison against base commit 5027323.

♻️ This comment has been updated with latest results.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.77%. Comparing base (5027323) to head (b4c05f7).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #5137   +/-   ##
========================================
  Coverage    52.77%   52.77%           
========================================
  Files          659      659           
  Lines        23589    23589           
  Branches      3881     3881           
========================================
  Hits         12448    12448           
  Misses        9994     9994           
  Partials      1147     1147           

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5027323...b4c05f7. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pull-request-size pull-request-size Bot added size/M and removed size/S labels Aug 5, 2026
@MohamadJaara
MohamadJaara marked this pull request as ready for review August 7, 2026 07:51
@MohamadJaara
MohamadJaara requested a review from a team as a code owner August 7, 2026 07:51
@MohamadJaara MohamadJaara changed the title ci: add trusted Gradle cache policy ci: add trusted Gradle cache policy [WPB-27819] Aug 10, 2026
@MohamadJaara

Copy link
Copy Markdown
Member Author

@claude review

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review: ci: add trusted Gradle cache policy [WPB-27819]

Collapsing five copy-pasted setup-java + WarpBuilds/cache + wrapper-validation blocks into one composite action is a real maintainability win, and the trust matrix in the description is the right way to reason about shared Gradle state. Below: what I verified as fine, then what I think will bite.

Verified as safe (so nobody has to re-check)

  • Wrapper validation is preserved. No longer an explicit step, but gradle/actions/setup-gradle@50e97c2 defaults validate-wrappers: true, so dropping the standalone steps loses no coverage. Worth a comment in the action so a future reader does not "restore" it.
  • Removing GRADLE_OPTS: "-Xmx6G -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g" from lint/compose-stability is a no-op: gradle.properties:19 already sets the identical org.gradle.jvmargs. The deleted "10GB heap" comment was stale. Only the client JVM reverts to the wrapper default, which is correct.
  • Shell hygiene is good: values via env: instead of interpolated into run:, quoted expansions, ${VAR-} defaults, newline guard before writing $GITHUB_ENV. Both zizmor: ignore[github-env] comments are justified.

High 1 — the heaviest job loses caching entirely

build-unified.yml's build matrix job runs on ubuntu-latest, not a Warp runner. It previously had actions/setup-java with cache: gradle; now the composite sets cache-disabled: true unconditionally (setup-java-gradle/action.yml:76) and every Warp step is gated on cache_backend.outputs.warp == 'true', false there. So the full ./gradlew app build (per flavor x variant) does a cold dependency download every run and gains nothing back — the opposite of the PR's goal, and silent.

Fix: stop hardcoding cache-disabled. setup-gradle's basic provider is GitHub Actions cache — ref-scoped, entry-level, with cache-cleanup — i.e. it already implements this trust model, enforced by GitHub rather than a hand-rolled gate:

    cache-provider: basic
    # Warp runners use WarpCache below; elsewhere let setup-gradle handle it.
    cache-disabled: ${{ steps.cache_backend.outputs.warp == 'true' || steps.cache_policy.outputs.enabled != 'true' }}
    cache-read-only: ${{ steps.cache_policy.outputs.read_only == 'true' }}

(cache-provider: basic is currently a no-op next to cache-disabled: true; this makes it meaningful.)

High 2 — task-output caching silently dropped in the unit-test job

gradle-run-unit-tests.yml:21-22 sets GRADLE_OPTS at job level. Enable Gradle build cache (action.yml:130-139) appends -Dorg.gradle.caching=true via $GITHUB_ENV, and job-level env: is layered on top of $GITHUB_ENV for subsequent steps — so for that job the flag is dropped with no error, while a (largely empty) warp-gradle-build-* entry is still saved.

More robust, and it also removes the need for the github-env zizmor exemption: append org.gradle.caching=true to "${GRADLE_USER_HOME:-$HOME/.gradle}/gradle.properties" instead. The Gradle user home file outranks the project gradle.properties, so it wins regardless of what any job puts in env:.

High 3 — backend detection fails open (into "no cache")

action.yml:60-68 hinges everything on the presence of WARPBUILD_RUNNER_VERIFICATION_TOKEN, an undocumented detail of the runner image. If Warp renames or stops exporting it, every cache step skips and all builds go cold — a gradual CI slowdown nobody will trace back here. Suggest (a) also accepting RUNNER_NAME matching warp-*, or an explicit cache-backend: auto|warp|github input, and (b) echoing enabled/read_only/backend to $GITHUB_STEP_SUMMARY so a misdetection is visible per run.

High 4 — transforms-* / jars-* globs likely match nothing on Gradle 9

The wrapper is on Gradle 9.4.1 (gradle-wrapper.properties:4). Since 8.7 the artifact-transform cache lives in the version-scoped dir (caches/<version>/transforms), not caches/transforms-*. So action.yml:87 / :103 (and the jars-* entries at :88 / :104) probably glob to nothing, meaning transform outputs — dexing, desugaring, AAR extraction, the biggest win after modules-2 — are not cached at all. The action already uses caches/*/... for generated-gradle-jars, kotlin-dsl and scripts, so this looks like an oversight. Worth one ls ~/.gradle/caches on a runner, then pinning the list to reality (caches/*/transforms, caches/*/dependencies-accessors, caches/*/javaCompile, caches/jars-* if present).


Medium

  1. Isolated Gradle homes leak disk. action.yml:23-28 creates ${RUNNER_TEMP}/gradle-home-<run>-<job>-<attempt> and nothing removes it; ./gradlew --stop only kills daemons. Each baseline-profile/QA run leaves a multi-GB tree on the long-lived office/android-qa runners until _temp is cleaned. Add rm -rf "${GRADLE_USER_HOME:?}" with if: always() after the stop step (order matters — daemon first, as you have it).
  2. Those isolated jobs now have no dependency cache at all. generate-baseline-profile.yml and both QA workflows previously had cache: gradle; they are schedule/workflow_dispatch, so policy resolves to enabled=false and the home is fresh — full re-resolve, re-download, cold daemon every run. generate-baseline-profile.yml has timeout-minutes: 90, which I would expect to get uncomfortably close. Isolation and caching are not in conflict here: a read-only restore into the isolated home buys the speed back without letting these refs write shared state.
  3. isolate-gradle-home and the cache paths disagree. Every path hardcodes ~/.gradle/... and ignores GRADLE_USER_HOME. Latent today, but the first workflow that combines isolate-gradle-home: true with a trusted develop trigger will silently save/restore the wrong directory. Derive paths from ${GRADLE_USER_HOME:-$HOME/.gradle}, or assert the two are mutually exclusive.
  4. Build-cache churn. warp-gradle-build-* keys include ${{ github.sha }} (:115, :125) and the path is the whole caches/build-cache-* dir, so every commit uploads a fresh copy of a monotonically growing local cache — and with setup-gradle caching off, its cache-cleanup pruning is off too. Expect eviction pressure that can push out the far more valuable modules-2 entry; worth capping (key rotation, or pruning entries older than ~7 days before save).
  5. Cleanup Gradle Cache coverage is uneven. Only lint, style and compose-stability strip modules-2.lock / gc.properties, but the unit-test and UI-test jobs now also save modules-2 via the composite. That cleanup belongs inside the action as a pre-save step.
  6. Security: the trust model rests on the cache backend, not on this gate. Two things worth a comment in the action: (a) for pull_request the workflow YAML comes from the base ref, but uses: ./.github/actions/... resolves from the checked-out PR head, so a PR author can edit action.yml and set enabled=true — the gate is defense-in-depth, not an enforcement boundary; (b) GitHub's cache enforces the isolation the "PR scope only" row claims — does WarpBuilds/cache implement equivalent per-ref scoping, or is it one flat namespace per repo? If flat, a same-repo PR writing warp-gradle-state-Linux-X64-<hash> is restorable by a later push to develop and the write column does not hold. Given the PR is framed as a trust model, that is the one thing I would confirm with Warp before merging.

Low / nits

  • Four near-identical Warp cache steps (:78, :94, :110, :120) duplicate the path list and the long hashFiles(...) key twice each. Emitting key/restore-keys as policy-step outputs would roughly halve that.
  • ${{ github.job }} is identical across matrix entries, so all build flavors contend for one build-cache key; losers log a reservation warning and discard their output. Relevant if that job ever moves to Warp runners.
  • generate-screenshots.yml is workflow_dispatch-only, so it goes from a full ~/.gradle/caches restore to none — defensible, but a behaviour change not called out in the description.
  • qa-runner-health-check.yml:23 still uses actions/setup-java directly. Fine (never invokes Gradle), but a one-line comment saying so prevents a future "you missed one" PR.
  • Test coverage: nothing exercises the policy matrix short of merging. The table in the description is the real spec — a small workflow_dispatch job that runs the resolve logic against synthetic EVENT_NAME/PR_BASE_REF/... values and asserts enabled/read_only would keep it honest. Failing that, the step-summary line above at least makes the resolved policy auditable.

Summary: right direction, much better structure than what it replaces. Before merging I would fix the ubuntu-latest build job losing caching outright and the transforms glob not matching Gradle 9 — together they mean this could land net-slower than the monolithic cache it replaces without ever showing up as a failure. The GRADLE_OPTS/$GITHUB_ENV shadowing and the fail-open Warp detection share that "silently degrades" quality and are cheap to make loud.

Review by Claude Code

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants