On CI you have both PR builds and a main-branch build. How should each participate in the remote cache, and how do you express that split so PR builds don't degrade the shared cache?
answer
- main = sole seeder
- PR builds pull-only
- reuse density vs eviction
- -PcacheSeed=true in pipeline
- don't branch-sniff in build code
basics
~20 sMake the main-branch (or release) job the only pusher and let PR builds pull read-only. Drive it from an invocation flag/property the seed job passes, e.g. isPush = isMainBranch, while every job keeps isEnabled = true to read.
solid answer
~40 sEven on CI, not every job should push. PR builds run churny, short-lived code; if they push, they flood the shared cache with entries almost nobody reuses, evicting the valuable main-branch entries and hurting hit rate. So treat the **main/release job as the seed** (the only pusher) and let PR builds **pull read-only**. Express it with a flag the seed job passes rather than branch-sniffing in build code: ```kotlin buildCache { remote<HttpBuildCache> { isEnabled = true // all CI jobs read isPush = startParameter.projectProperties["cacheSeed"] == "true" } } ``` The main-branch pipeline invokes `./gradlew build -PcacheSeed=true`; PR pipelines omit it. PR builds still get fast pulls from main's seed, but contribute nothing, keeping the cache stable and high-value. This is the CI-internal refinement of the broader CI-pushes/devs-pull split.
code
bash · 5 lines# main-branch pipeline step (the seed job)
./gradlew build -PcacheSeed=true
# pull-request pipeline step (pull-only)
./gradlew buildgo deeper
Knows CI pushes and devs pull; may not distinguish PR vs main.
Restricts push to the main/seed job and keeps PR builds pull-only via a flag.
Reasons about reuse density, eviction, and keeping branch logic out of build code.
Sets the org seeding strategy (which jobs seed, eviction/quota policy) and the invocation contract across pipelines.
## The problem with 'all CI pushes' The simple rule is "CI pushes, developers pull." But CI itself has heterogeneous jobs: - **Main/release builds** build stable, long-lived code that everyone branches from. Their outputs are exactly what other builds will want — high reuse. - **PR/feature builds** build transient code. Their task outputs are rarely reused by anyone else, because the PR will merge or close soon. If they push, they fill the cache with low-value entries that can **evict** the valuable main entries (caches have finite size and eviction policies), lowering the overall hit rate. ## The refinement: seed from main only Let the **main-branch (or tagged release) job be the sole seeder** — `isPush = true` there — and have **every** CI job (PR and main) keep `isEnabled = true` so they all benefit from pulls. PR builds get fast incremental hits from main's seed without polluting it. ## Express it via invocation, not branch-detection in build code Keep build logic environment-agnostic and let the CI pipeline express intent through a property or task: ```kotlin // settings.gradle.kts buildCache { remote<HttpBuildCache> { url = uri("https://cache.example.com/cache/") isEnabled = true isPush = startParameter.projectProperties["cacheSeed"] == "true" } } ``` Pipeline side: ```bash # main-branch job ./gradlew build -PcacheSeed=true # pull-request job ./gradlew build # no flag → pull-only ``` This keeps the decision in the pipeline definition (where branch context naturally lives) rather than hard-coding `if (branch == 'main')` inside Gradle, which is brittle and couples the build to CI internals. ## Why not push from PRs 'just in case'? Because cache value is about **reuse density**. Entries that only one ephemeral build will ever read are pure cost: storage, eviction pressure, and noise in hit diagnostics. Concentrating writes on the stable seed maximizes the ratio of useful entries. ## Combined picture - Developers: `isEnabled = true`, `isPush = false` (read-only). - PR CI: `isEnabled = true`, `isPush = false`. - Main/release CI (the seed): `isEnabled = true`, `isPush = true`. All three pull; only the seed writes. That is the complete push/pull-and-seeding strategy for a shared cache.
- Why can PR builds pushing actually lower the overall cache hit rate?Caches are finite and evict. PR entries are rarely reused but consume space, evicting valuable main-branch entries that many builds need, so hit rate drops.
- Why pass a -P flag instead of writing `if (branch == 'main')` in the build script?Branch context belongs in the CI pipeline. Keeping the build environment-agnostic and gating on an explicit flag avoids coupling Gradle logic to CI internals and is easier to test.
saying these in an interview costs you the question
- Letting every CI job push 'to maximize cache fill' — degrades reuse density.
- Hard-coding branch names inside settings.gradle to decide pushing.