SOFTWARE / SYSTEMS / AIEngineering news. Technical depth.
Architecture / 4 MIN READ

What must be included in a build cache key to prevent incorrect cache hits?

A correct build cache key must cover every input that can change outputs: source files, dependency state, command lines, environment, and platform identity.

A build cache key must include every input that can change the build output: declared source inputs, dependency state, output names, command line or compiler flags, relevant environment variables, and platform identity such as OS, architecture, and runtime version. If any of those are missing, you can get a cache hit for artifacts that were built under different conditions. That is why “clearing the cache” sometimes fixes a release: it bypasses an incomplete key rather than solving the underlying correctness problem.

What counts as a complete cache key?

At the build-system level, the safe rule is simple: hash the full action definition, not just the source tree. Bazel’s remote caching documentation says each action is defined by its declared inputs, output filenames, command line, and environment variables. That is the right mental model even if you use another tool.

For application teams, that usually means including:

  • source files and generated-input files the task reads
  • dependency lockfiles and package manifests
  • build configuration such as tsconfig, babel config, webpack config, or compiler config
  • command-line flags and optimization modes
  • environment variables that influence output
  • platform and runtime identity when outputs differ by machine context

If a task reads something and the cache key does not, the key is incomplete.

Why do hidden inputs cause “good” cache hits with bad artifacts?

Because the cache is doing exactly what you asked. If API_URL, NODE_ENV, a feature flag, or a compiler switch changes the emitted files but is absent from the hash, the cache sees two different builds as equivalent.

Turborepo’s cache debugging guide is explicit here: a missing environment variable in env or a missing file in inputs can produce stale outputs, and --summarize will show “all hash inputs” for comparison between runs in JSON output from the tool’s run summaries in the Turborepo gotchas guide.

How should you handle platform identity and runtime differences?

Include them whenever artifacts are not portable across environments. Turborepo’s platform-handling guide recommends accounting for OS, architecture, and Node.js version differences in the cache hash. It shows one practical approach: write platform details to a file and add that file to task inputs or global dependencies.

That pattern generalizes well. If your native addon, container layer, or compiled binary differs between Linux and macOS, your cache key must differ too. If your build output is truly platform-independent, adding platform identity only reduces reuse and is unnecessary.

What does a concrete worked example look like?

Prerequisite: a Turborepo task whose build output depends on an environment variable and on platform.

Suppose web embeds API_URL at build time and also packages a native dependency.

{
  "globalDependencies": ["turbo-cache-key.json"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local", "tsconfig.json"],
      "env": ["API_URL", "NODE_ENV"],
      "outputs": ["dist/**"]
    }
  }
}

Then generate turbo-cache-key.json before the build with the current OS and architecture, as shown in the platform guide. Now test correctness, not just speed:

  1. Run a build and save the summary.
  2. Change only API_URL; rerun and confirm a cache miss.
  3. Restore API_URL; change only platform or runtime version; confirm a miss if outputs differ.
  4. Run once with --force to verify the task still succeeds uncached.
  5. Diff --summarize outputs between runs to confirm the changed input is actually present in the hash.

That enforced boundary matters: generate the platform file before invoking the build tool, so the hash is computed from the updated file rather than from a stale preflight artifact.

Why does clearing the build cache fix our release?

Because it forces recomputation and avoids reusing a bad artifact keyed by incomplete inputs. Treat that as evidence of a missing hash contributor, not as a permanent remedy.

What should the team verify before adopting this workflow?

Verify that every build-time reader is represented in the key, especially env vars, config files outside the package, lockfiles, and platform/runtime inputs. Then prove it with small invalidation experiments before trusting shared or remote cache reuse.

Next step: pick one flaky build, list every non-source input it reads, add each one to the cache key, and validate the result with paired --summarize and --force runs.

Reviewed: 2026-09-05

SOURCES & REVIEW

Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.

Read our editorial approach ↗