skip to content

What does doNotTrackState() do, when would you use it, and what are the consequences?

level: seniorimportance: should knowfreq 30%

answer

  1. marks task untracked
  2. always runs, never cached
  3. needs a reason string
  4. for non-reproducible side effects
  5. not a fix for churning inputs

basics

~10 s

doNotTrackState() tells Gradle a task has no reliable up-to-date state, so Gradle skips snapshotting its inputs/outputs and always runs it. The cost: the task never shows UP-TO-DATE and is never cached.

solid answer

~50 s

`doNotTrackState("reason")` is a `Task` method that opts the task out of input/output state tracking. Gradle no longer fingerprints its inputs or outputs, so up-to-date checking can't conclude the task is current — it executes on every invocation and is excluded from the build cache. You use it when a task's behavior is genuinely non-deterministic or has side effects Gradle can't model: probing a live external service, talking to a daemon with mutable state, or producing output that's never reproducible. It's the honest declaration "I have no stable state to compare." The trade-off is losing all incremental benefit for that task, so it should be a narrow, last resort — preferred over silently lying with `@Internal` on a real input, but inferior to making the task deterministic. The string argument documents *why*, which surfaces in build scans and validation.

code

kotlin · 4 lines
kotlin
tasks.register("probeEnvironment") {
    doNotTrackState("Reads live host state; not reproducible")
    doLast { println("always executes") }
}

go deeper

for a junior

Know that doNotTrackState makes a task always run and never cache.

for a middle

Explain the trade-off and a valid use case (non-reproducible external work).

for a senior

Contrast it with @Internal and upToDateWhen{false}, and judge when it's the wrong tool.

for a principal

Treat each usage as an incrementality hole to audit; set policy that requires justification and review for untracked tasks.

## What it is `Task.doNotTrackState(reason: String)` marks a task as **untracked**. Normally Gradle snapshots a task's declared inputs and outputs to decide UP-TO-DATE and to enable caching. An untracked task skips that snapshotting entirely. ## What changes when you call it - The task **always executes** — it can never be reported `UP-TO-DATE`. - The task is **never stored in or restored from the build cache** (no input key to compute reliably). - Gradle relaxes some output-overlap validations, because it no longer owns the task's output state. - The supplied reason string is recorded and shown in diagnostics (e.g., build scans). ```kotlin tasks.register("refreshFromLiveApi") { doNotTrackState("Fetches volatile data from a live endpoint; output is never reproducible") doLast { // always run; result depends on external mutable state } } ``` ## When it's appropriate 1. **Non-reproducible side effects** — a task that queries a live service, current network state, or wall-clock-dependent external system. 2. **Tasks that mutate shared/external state** Gradle can't fingerprint (e.g., touching a directory another tool also writes). 3. **Legacy/opaque tooling** where you cannot enumerate the real inputs/outputs accurately. ## When it's the WRONG tool - To silence up-to-date churn from a *fixable* non-deterministic input (a baked-in timestamp). Make the input deterministic instead. - As a substitute for declaring inputs/outputs. `@Internal` and proper declarations are correct for trackable tasks; `doNotTrackState` is for genuinely untrackable ones. ## Relationship to other opt-outs - `@Internal` excludes a single *property* from tracking but the task is still up-to-date-checked on its other properties. - `doNotTrackState()` excludes the *whole task* from up-to-date checking and caching. - `outputs.upToDateWhen { false }` forces a task out of date based on a predicate but still tracks state; `doNotTrackState` goes further by skipping snapshotting altogether. ## The mental model Think of it as the task author admitting: "My result has no stable fingerprint, so don't pretend it does." That honesty keeps builds correct (no stale outputs) at the cost of always paying for the task. Audit its usage — every `doNotTrackState` is a permanent hole in incrementality.

  • How is doNotTrackState() different from outputs.upToDateWhen { false }?
    upToDateWhen { false } still snapshots inputs/outputs but forces the task out of date via a predicate; doNotTrackState skips snapshotting entirely and also disables caching. The latter is the right choice when there's genuinely no reliable state.
  • Does an untracked task break tasks that depend on its output?
    No — dependents still wire to its outputs and run after it. But because the untracked task always runs, dependents may also re-run more often, so apply it narrowly.
  • Why does it require a reason string?
    To document the justification; the reason surfaces in build scans and reviews so the deliberate loss of incrementality is visible and auditable.

saying these in an interview costs you the question

  • Using it to mask a non-deterministic input you could make stable.
  • Claiming an untracked task can still be FROM-CACHE — it can't.
  • Confusing it with @Internal, which only excludes one property.

context