Debugging Why a Turborepo Task Is Never Cached
A task always executes and never replays from cache, even on identical inputs. This page shows how to find why Turborepo refuses to cache a task and how to make it cacheable.
Exact symptoms and error messages
web:build: cache miss, executing 1a2b3c...
# run again, no changes
web:build: cache miss, executing 1a2b3c...
Root cause analysis
Turborepo will not cache a task that declares no outputs, is marked cache: false, is persistent, or writes outside its declared output globs. Without knowing what a task produces, Turborepo has nothing to store or restore, so it re-executes every time. Correct output declaration is core to Remote Caching Setup and the deterministic-output rules in Turborepo Pipeline Configuration.
Turborepo will not cache a task it cannot describe the outputs of. If a task declares no outputs, is explicitly cache: false, is marked persistent, or writes files outside its declared output globs, there is nothing for Turborepo to store or restore, so it re-executes every time. The fix is almost always to declare what the task produces accurately, so the cache has something concrete to save and replay.
A subtler cause is outputs written outside the declared globs. If build declares dist/** but also emits a manifest to .cache/, that file is invisible to the cache — restoring from cache produces an incomplete result, so in practice you disable caching to avoid the corruption, and the task never replays. Declaring every output path is what makes a cached restore complete and therefore trustworthy.
Resolution and configuration patch
Declare the task's outputs (and remove any accidental cache: false):
// turbo.json
{
"tasks": {
"build": {
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"cache": true
}
}
}
Ensure the build writes only into declared paths; artifacts written elsewhere are invisible to the cache and defeat replay.
CLI validation and debug commands
# See why a task is not cached
pnpm turbo run build --summarize
# Confirm outputs are declared and produced
pnpm turbo run build --dry=json | jq '.tasks[0].outputs'
Prevention and CI guardrails
- Declare
outputsfor every cacheable task; an empty list disables caching. - Reserve
cache: falsefor genuinely non-deterministic tasks only. - Keep
persistenttasks (dev servers) out of the cached build graph. - Verify with
--summarizethat produced files fall inside declared output globs.
Declaring outputs completely
The single most common reason a task never caches is an incomplete outputs list. Turborepo caches exactly the files you declare, so any artifact the task produces but does not list is lost on a cache restore — which makes caching unsafe and effectively forces you to turn it off.
// turbo.json
{
"tasks": {
"build": { "outputs": ["dist/**", ".next/**", "!.next/cache/**"] }
}
}
List every directory the build writes, and use negations to exclude volatile subpaths (like .next/cache) that should not be part of the cached artifact. The test is simple: delete the outputs, restore from cache, and confirm the result is byte-complete. If anything is missing, the outputs list is too narrow — widen it until a restore reproduces the full build, at which point the task caches correctly and replays instead of re-running.
Persistent and non-deterministic tasks
Some tasks genuinely should not cache, and recognizing them prevents wasted debugging. A dev server is persistent — it never exits, so it has no final output to store. A task that embeds a timestamp or a random value is non-deterministic, so a 'cached' result would be wrong. For these, disabling caching is correct, not a bug.
{
"tasks": {
"dev": { "persistent": true, "cache": false },
"build": { "outputs": ["dist/**"] }
}
}
Mark long-running tasks persistent so Turborepo keeps them out of the cached graph, and reserve cache: false for tasks whose output legitimately varies run to run. The goal is to cache everything deterministic and exclude only what truly cannot be — rather than blanket-disabling caching because one task misbehaves, which throws away the replay benefit for every well-behaved task alongside it.
Frequently Asked Questions
Why does a task with no outputs still not cache?
Turborepo caches a task's declared outputs; with none declared it has nothing to store, so it cannot replay and re-runs every time. Declare the files the task produces.
Should dev servers be cached?
No — mark long-running tasks persistent: true and keep them out of the cached build graph. Caching only makes sense for tasks that produce deterministic file outputs and then exit.
Why does a task with no outputs never cache?
Turborepo caches a task's declared outputs; with none declared it has nothing to store, so it cannot replay and re-runs every time. Declare every directory the task produces.
How do I know my outputs list is complete?
Delete the outputs, restore from cache, and check the result is byte-complete. Anything missing means the list is too narrow — widen it (with negations for volatile subpaths) until a restore reproduces the full build.
Should a dev server be cached?
No. Mark it persistent: true so Turborepo keeps it out of the cached graph. Caching only applies to tasks that produce deterministic file outputs and then exit.
Related
- Remote Caching Setup — the shared remote cache configuration this fix builds on.