Skip to content

build(turbo): declare content/docs as an input to build and type-check - #85

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-81-turbo-content-inputs
Aug 18, 2026
Merged

build(turbo): declare content/docs as an input to build and type-check#85
os-zhuang merged 1 commit into
mainfrom
claude/issue-81-turbo-content-inputs

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#81

content/docs/ lives at the repo root, outside the apps/docs package, but both build and type-check genuinely consume it — apps/docs/source.config.ts points fumadocs at path.resolve(process.cwd(), '../../content/docs'). With no inputs declared, turbo's default hash covers only the package directory, so a content-only change did not move the hash and both tasks replayed a cached green.

 "build": {
"dependsOn": ["^build"],
+ "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/content/docs/**"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"type-check": {
- "dependsOn": ["^build"]+ "dependsOn": ["^build"],+ "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/content/docs/**"]
}

turbo version in this repo: 2.9.14 (root package.json pins ^2.5.0; lockfile resolves 2.9.14).

The defect, confirmed from history before touching anything

The issue quoted cache hit, replaying logs 6eb08f6bec2cc211 from a sibling worktree. A fresh worktree cut from today's origin/main — which has since merged two rounds of content changes (8fe88ba, merged as #82) — computes the same hash:

$ turbo run type-check --filter=@objectos/docs --dry=json
type-check inputs: 37 total, 0 matching 'content'
hash: 6eb08f6bec2cc211

37 inputs, all under apps/docs/, none from content/docs/. The hash is unchanged across every content change that has landed since the issue was filed.

1. Before — a content-only change hits the cache

On the unmodified origin/main config, isolated TURBO_CACHE_DIR:

===== STEP 2: no-op re-run (expect cache hit) =====
Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 55ms >>> FULL TURBO
===== STEP 3: change confined to content/docs/** =====
M content/docs/index.mdx
--- re-run after the content change ---
Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 49ms >>> FULL TURBO
hash after content edit: 6eb08f6bec2cc211

Same hash, FULL TURBO, with a modified MDX page in the working tree. Nothing was checked.

2 and 3. After — it misses when it should, hits when it should

The config is held constant across the three steps below; the only variable is the content file, which is edited and then reverted. The hash moves and returns to the identical value, so the miss cannot be attributed to a lockfile touch, a config change, or a different working directory.

type-check

--- 2. no-op re-run [BAR #3: cache must still hit] ---
Cached: 1 cached, 1 total
Time: 69ms >>> FULL TURBO
hash(clean) = 7ec62389ee49c559
--- 3. change confined to content/docs/** [BAR #2: must MISS and execute] ---
M content/docs/index.mdx
Cached: 0 cached, 1 total
Time: 7.475s
hash(content edited) = 5e646c077c805631
--- 4. revert the content change; nothing else touched ---
(empty = restored)
Cached: 1 cached, 1 total
Time: 66ms >>> FULL TURBO
hash(restored) = 7ec62389ee49c559

build

--- 2. no-op re-run [BAR #3] ---
Cached: 1 cached, 1 total
Time: 147ms >>> FULL TURBO
hash(clean) = df9c5ef0f979f8c6
--- 3. change confined to content/docs/** [BAR #2] ---
Cached: 0 cached, 1 total
Time: 1m32.414s
hash(content edited) = 39b8ad052a391947
--- 4. revert ---
Cached: 1 cached, 1 total
Time: 2.261s >>> FULL TURBO
hash(restored) = df9c5ef0f979f8c6

7ec62389ee49c5595e646c077c8056317ec62389ee49c559, and df9c5ef0f979f8c639b8ad052a391947df9c5ef0f979f8c6. Caching is not disabled; the hash simply now tracks the content.

Which lever — measured, not inferred

Four configs, each dry-run on a clean tree and again with one content/docs/ file edited:

configcontent files in type-check inputstasks whose hash moves on a content edit
A. origin/main (no inputs)0 of 37none — the defect
B. inputs: ["$TURBO_DEFAULT$", "../../content/docs/**"]413 of 450build, type-check
C. inputs: ["$TURBO_DEFAULT$", "$TURBO_ROOT$/content/docs/**"]413 of 450build, type-check
D. root globalDependencies: ["content/docs/**"]0 of 37build, type-check, and lint

Answering the card's open question directly: the package-relative escape is honoured in turbo 2.9.14 — B is not broken. B and C produce byte-identical results (same 450-entry input list, same hashes on both legs). globalDependencies is the wrong lever: it feeds the global hash rather than the task's inputs, so content/docs/ never appears as a task input and every task in the repo is invalidated, including lint, which does not read content.

Why $TURBO_ROOT$ over ../../

Because a wrong inputs glob is not an error. Every one of these exits 0:

--- inputs glob: '$TURBO_ROOT$/content/docs/**' ---
exit=0 (NO ERROR) inputs=450 content-matching=413 hash=7ec62389ee49c559
--- inputs glob: '../../content/docs/**' ---
exit=0 (NO ERROR) inputs=450 content-matching=413 hash=7ec62389ee49c559
--- inputs glob: '../../content/docs-TYPO/**' ---
exit=0 (NO ERROR) inputs=37 content-matching=0 hash=6eb08f6bec2cc211
--- inputs glob: '../content/docs/**' ---
exit=0 (NO ERROR) inputs=37 content-matching=0 hash=6eb08f6bec2cc211
--- inputs glob: '../../../content/docs/**' ---
exit=0 (NO ERROR) inputs=37 content-matching=0 hash=6eb08f6bec2cc211

A miscounted ../ reverts silently to 6eb08f6bec2cc211 — the exact defective hash, with no diagnostic. ../../ encodes the package's depth and is only correct while docs sits at apps/docs; $TURBO_ROOT$ is anchored to the repo root by turbo itself. Same behaviour today, one fewer thing that can be silently wrong tomorrow. The microsyntax is validated by turbo (it carries dedicated errors for $TURBO_ROOT$ not at the start of a glob, and for a missing / after it), so a typo in the token itself fails loudly.

AGENTS.md

A short section documents the coupling, the reason for $TURBO_ROOT$, how to check that the hash actually moves (since a wrong glob is silent), and --force as a belt-and-braces escape hatch — explicitly the escape hatch, not the routine path. --force also covers anyone on a turbo older than 2.4, where $TURBO_ROOT$ does not exist and the glob would match nothing.

Verification

All at 749e62c:

pnpm turbo run type-check --continue --force 1 successful, 1 total exit=0
pnpm turbo run build --force 1 successful, 1 total exit=0
pnpm turbo run test 0 successful, 0 total exit=0 (#72, unchanged by this PR)
check-translation-ownership.mjs exit=0 (0 translation artifacts, 2 other files)
check-translations.mjs exit=0 translations gate passed
check-translation-output.mjs --self-test exit=0 20 cases, every rule demonstrated able to fail

Scope


Generated by Claude Code

`content/docs/` lives at the repo root, outside the `apps/docs` package, but
both `build` and `type-check` consume it — `source.config.ts` points fumadocs
at `../../content/docs`. With no `inputs` declared, turbo's default hash covers
only the package directory, so a content-only change did not move the hash and
both tasks replayed a cached green. The cache is shared across worktrees in a
multi-agent container, so the replayed logs could come from a sibling agent's
tree, which makes the false green look like a real run.
Declare the dependency with the root-anchored `$TURBO_ROOT$` microsyntax rather
than a hand-counted `../../`. Both resolve identically on turbo 2.9.14 (measured:
same 450-entry input list, same hashes), but a relative glob encodes the
package's depth and silently matches nothing if the package moves — and a wrong
inputs glob is not an error, it exits 0 and reverts to the stale hash.
Task-level `inputs` rather than root `globalDependencies`: globalDependencies
feeds the global hash, so it would bust every task in the repo, including `lint`,
which does not read content/docs (measured).
AGENTS.md documents how to verify the hash actually tracks content, plus the
`--force` escape hatch for anyone on a turbo older than 2.4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJPxtTxoxTUnjNdTbiEaRa
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

turbo type-check / build declare no inputs, so a content-only change replays a cached green locally

2 participants

@os-zhuang@claude