Turborepo · advanced
Turborepo cache miss: determine whether inputs or environment changed
Diagnose Turborepo cache misses by separating input-graph changes from environment drift, using the task's hash metadata and Turbo's own logging to isolate whether files, env vars, dependencies, or daemon state invalidated the entry.
The symptoms
- •turbo run <task> prints "cache miss" or ">>> FULL TURBO" and re-executes the task even on a workspace where it previously hit cache.
- •Identical commands produce a cache hit on one developer machine but a miss on CI for the same commit hash.
- •Logs include lines such as "cache miss (input hash changed)" or "cache miss (env changed)" rather than the generic "cache miss" string.
- •Local builds hit cache only when the daemon is restarted, or only when run with turbo run --no-daemon.
- •Cache hits collapse to misses immediately after editing dotenv, .env, or files outside declared inputs.
Likely causes
- •Input hash drift: files matched by the task's `inputs` glob changed, or a previously implicit input was not declared.
- •Environment drift: values of keys in the task's `env` or `passThroughEnv` arrays differ between the two runs (CI vs local, branch vs main, shell vs CI runner).
- •Dependency hash drift: an upstream task declared in `dependsOn` changed its outputs or hash inputs, forcing downstream re-execution.
- •Output and global hash drift: `.turbo/cache` was cleared, the workspace moved, or `globalDependencies` (e.g., turbo.json) was modified.
- •Cache-store mismatch: local cache uses filesystem or `Turborepo_MMAP_AUTH` while CI uses a remote V8/Remote cache that lacks the prior artifact, producing a miss on one side only.
First ten minutes
- 01Reproduce on demand: run `turbo run <task> --summarize` twice in a row from a clean state; record whether the second run reports "cache hit" or "no cache entry".
- 02Set `TURBO_LOG_VERBOSITY=debug` (or pass `--log-level=debug`) so each task prints the hash inputs it computed, per the caching docs.
- 03Diff the two runs: capture the task's printed `hash` and the named reason for any miss (`inputs changed`, `env changed`, `output missing`, etc.).
- 04Inspect `turbo.json` for that task's `inputs`, `outputs`, `env`, `passThroughEnv`, and `dependsOn` so you know which fields feed the hash.
- 05Verify the cache store: confirm both runs resolve the same `TURBO_API` / `TURBO_TOKEN` / local cache directory; a mismatch between local-only and remote cache is itself a cause.
Evidence to collect
- •Full Turbo debug log from one cache-hit run and one cache-miss run of the same task on the same commit.
- •Resolved value of `turbo.json#tasks.<task>.inputs`, `.outputs`, `.env`, `.passThroughEnv`, and `.dependsOn` for the affected task.
- •List of files actually changed between the two runs (`git diff --name-only <shaA> <shaB>`) cross-checked against the declared `inputs` glob.
- •Environment snapshot (`env | sort` filtered to keys named in `env`/`passThroughEnv`, or `printenv`) on both runners to find drifted values.
- •Remote-cache reachability proof: HTTP 200 from the configured `TURBO_API` endpoint showing the artifact either present or absent for the computed hash.
Where to look
- •Boundary: the Turborepo hash-computation layer for a single task, not the task's own script. The decision is made before the script runs.
- •Boundary: `turbo.json` schema fields `inputs`, `outputs`, `env`, `passThroughEnv`, `globalDependencies`, and `globalEnv`.
- •Boundary: filesystem inputs matched by `inputs` globs, including dotfiles in workspace roots that Turbo hashes by default.
- •Boundary: process environment inherited by the Turbo CLI, filtered by the task's `env` declaration before hashing.
- •Boundary: the cache backend (local `.turbo/cache` vs a remote V8 cache) that decides whether a hash maps to a stored artifact.
Diagnostic steps
- 01Run the failing task twice with `--summarize --log-level=debug` and diff the per-task `hash` field. Identical hashes with a miss point at the cache store; different hashes point at input/env drift.
- 02If hashes differ, parse the debug log for the named input sets (`files`, `dependencies`, `env`, `global`, `task`) to see which set changed. The Turborepo caching docs describe these input groups.
- 03For `files` changes, compare `git diff --name-only` against the declared `inputs` glob. An undeclared file inside the glob range will still be hashed; a declared file outside the glob will not.
- 04For `env` changes, print the filtered environment on each runner using only the keys listed in `env`/`passThroughEnv` and diff them; Turborepo only hashes the keys you declare.
- 05For `dependencies` changes, run `turbo run <task> --dry=json` and inspect the dependency graph to find which upstream task's hash differs.
- 06For cache-store issues, run `turbo run <task> --summarize --log-level=debug` with `--cache` options enabled and confirm whether the log shows a remote lookup; absence of remote calls implies local-only cache.
- 07If only the daemon is involved, retry with `--no-daemon`; if the miss disappears, the daemon was holding stale task-graph state and the issue is environmental, not in `turbo.json`.
Common mistakes
- •Adding files to `inputs` "to be safe" — this expands the hash surface and turns previously cacheable tasks into misses; only declare inputs the task actually reads.
- •Treating any "cache miss" log line as a remote-cache failure without checking whether the local hash itself changed first.
- •Assuming two runners with the same commit will produce the same hash — env vars in CI (e.g., `CI`, `BUILD_NUMBER`) often differ and must be listed in `env`/`passThroughEnv` or excluded.
- •Comparing only script exit codes; Turbo's miss decision is made before the script executes, so a script-level fix cannot recover a missed entry.
- •Hashing files via `.gitignore`-style exclusions without declaring them in `inputs`; the hash walks the filesystem based on the glob, not on ignore rules.
Safe fixes
- •If evidence shows a real file change inside the declared `inputs` glob: accept the miss as correct behavior; no fix required, document the input boundary in `turbo.json`.
- •If evidence shows an env var drift not relevant to outputs: add the var to `passThroughEnv` so Turbo ignores it, or remove it from `env` if it was mis-declared; verify by re-running and confirming the hash is unchanged.
- •If evidence shows an undeclared file forcing invalidation: extend the task's `inputs` glob only if the file genuinely affects outputs; otherwise leave the default and confirm the miss is acceptable.
- •If evidence shows the daemon held stale state: switch to `--no-daemon` for diagnosis, then restart the daemon and re-run; do not change `turbo.json` for a daemon-only symptom.
- •If evidence shows remote-cache absence for an otherwise valid hash: confirm `TURBO_API` and `TURBO_TOKEN` resolve to the same team on both runners before changing any task definition.
Prove the fix
- 01Two consecutive `turbo run <task>` invocations on an unchanged tree report "cache hit" with identical `hash` values in `--summarize` output.
- 02The debug log shows no `inputs changed`, `env changed`, `output missing`, or `dependencies changed` reason between the two runs.
- 03Re-introducing the originally drifted input (file edit, env var change) reproduces a miss with the matching `cache miss (<reason>)` line, confirming the fix is targeted and not accidental.
- 04CI and local runners produce the same `hash` string for the same commit when filtered env and declared inputs are aligned.
- 05Remote-cache presence can be demonstrated via the configured `TURBO_API` returning HTTP 200 with the artifact metadata for the recorded hash.
Prevention and next steps
- •Maintain a written contract per task in `turbo.json`: list the exact `inputs` glob, `outputs`, `env`, and `passThroughEnv`, and review it whenever the task script changes.
- •Standardize CI environment by exporting only the variables that should influence hashing, and declare every required one in `env`.
- •Treat cache-miss reasons as first-class signals: monitor for `inputs changed`, `env changed`, and `dependencies changed` lines in CI logs to detect drift early.
- •Keep `.turbo/cache` location and remote-cache team configuration identical between local dev and CI to avoid store-side misses masquerading as input misses.
Safe commands and checks
turbo run <task> --summarize --log-level=debug
turbo run <task> --no-daemon --summarize --log-level=debug
turbo run <task> --dry=json | jq '.tasks[] | {task, hash, inputs}'
TURBO_LOG_VERBOSITY=debug turbo run <task> --summarize
git diff --name-only <shaA> <shaB>
printenv | grep -E '^(CI|BUILD_NUMBER|TURBO_)' | sort
grep -E '\"(inputs|outputs|env|passThroughEnv|dependsOn)\"' turbo.json