Your custom task uses @InputFiles for a jar classpath, and the build cache never hits across CI agents. Walk through diagnosing and fixing the input normalization.
answer
- caching.debug / --scan to see fingerprints
- @InputFiles defaults to ABSOLUTE
- @Classpath = path-insensitive + jar-normalized
- reproducible archives: no timestamps, stable order
- convention plugin + validation to prevent regression
basics
~20 sPlain @InputFiles defaults to ABSOLUTE path sensitivity and full byte content, so jar timestamps and agent paths bust the fingerprint. Switch the property to @Classpath (or @CompileClasspath for compile inputs) so paths are ignored and jars are normalized, making the fingerprint portable.
solid answer
~40 sFirst confirm the symptom with `--scan` or `-Dorg.gradle.caching.debug=true`, which prints why a task missed the cache and what its input fingerprint was. A classpath declared as bare `@InputFiles` defaults to ABSOLUTE path sensitivity and hashes full jar bytes — so per-agent absolute paths and non-reproducible jar metadata (timestamps) make every agent's fingerprint unique. The fix is to declare the input with classpath semantics: `@Classpath` for a runtime classpath (order-significant, jar-normalized, path-insensitive) or `@CompileClasspath` if the input feeds compilation (ABI-only). That alone usually restores cross-agent hits. If non-reproducible jars are also involved, complement it by making upstream archives reproducible (preserveFileTimestamps = false, reproducibleFileOrder = true). At org scale, encode the classpath annotation in a convention plugin and validate it, so no custom task ships a classpath as plain @InputFiles again.
code
kotlin · 9 linestasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
abstract class LaunchTask : DefaultTask() {
@get:Classpath
abstract val classpath: ConfigurableFileCollection
}go deeper
Recognize that a plain @InputFiles classpath caches poorly and @Classpath is the better annotation.
Explain ABSOLUTE-default vs @Classpath normalization and use caching.debug to diagnose a miss.
Diagnose both wrong-normalization and non-reproducible-jar causes and apply both fixes.
Treat it as governance: encode normalization in convention plugins, add validation/CI guards, and monitor shared-cache hit-rate to prevent regressions across the org.
## Step 1 — Reproduce and observe Enable cache diagnostics: run with `--build-cache -Dorg.gradle.caching.debug=true` or publish a build scan with `--scan`. The output shows each task's individual input property fingerprints and the resulting cache key. Compare the same task across two agents: you'll see the classpath property's hash differs even though the dependencies are identical. ## Step 2 — Identify the root cause Two failure modes typically combine: 1. **Wrong normalization.** A property declared `@InputFiles` (without classpath annotation) defaults to `ABSOLUTE` path sensitivity. Agent A resolves jars under `/agentA/caches/...`, agent B under `/agentB/...`; the absolute paths differ, so the fingerprint differs and the cache never matches. 2. **Non-reproducible jar content.** Even with paths normalized, jars built with embedded timestamps or unstable entry order hash differently per build, busting the fingerprint. ## Step 3 — Apply classpath semantics Replace the generic annotation with a classpath-aware one: ```kotlin abstract class LaunchTask : DefaultTask() { // BEFORE: order/path/byte sensitive, defaults to ABSOLUTE // @get:InputFiles abstract val classpath: ConfigurableFileCollection // AFTER: path-insensitive, jar-normalized runtime classpath @get:Classpath abstract val classpath: ConfigurableFileCollection } ``` `@Classpath` ignores the absolute location of jars and normalizes their internal entries, so two agents resolving the same dependencies fingerprint equally. If the classpath feeds a compiler, use `@CompileClasspath` instead to additionally collapse non-ABI changes. ## Step 4 — Make jars reproducible For archives you produce, ensure deterministic content so even content hashing is stable: ```kotlin tasks.withType<AbstractArchiveTask>().configureEach { isPreserveFileTimestamps = false isReproducibleFileOrder = true } ``` ## Step 5 — Prevent regression at scale This is a governance problem, not a one-off. Put the classpath annotation in your **convention plugin's** task types, and rely on Gradle's task validation (which warns when a cacheable task's file input lacks proper normalization) plus a CI check that no custom classpath input is declared as plain `@InputFiles`. Track shared-cache hit rate as a metric; a sudden drop usually means a new task shipped with the wrong normalization. ## Summary The portability of a classpath input is governed by its declared normalization. Generic `@InputFiles` is ABSOLUTE and byte-exact; `@Classpath`/`@CompileClasspath` are path-insensitive and jar/ABI-normalized. Choosing the right annotation — and keeping archives reproducible — is what makes a remote cache actually shared across agents.
- How do you confirm WHY a task missed the build cache?Run with -Dorg.gradle.caching.debug=true (or use a --scan) to print each input property's fingerprint and the cache key. Diff the classpath property's hash across two agents to see the location/byte difference.
- After switching to @Classpath the cache still misses sometimes — what else?Likely non-reproducible jars: embedded timestamps or unstable entry order make the normalized content hash differ. Set preserveFileTimestamps=false and reproducibleFileOrder=true on archive tasks upstream.
- Why prefer @CompileClasspath if the input feeds a compiler?It fingerprints only the ABI, so recompiled-but-unchanged-API upstream jars don't bust the consumer's fingerprint, adding compile avoidance on top of path portability.
saying these in an interview costs you the question
- Blaming the remote cache infrastructure before checking input normalization.
- Switching everything to ABSOLUTE 'to be safe' — that guarantees non-portability.
- Fixing one task by hand without addressing the convention-plugin/validation gap that lets it recur.