Skip to content

fix(build): declare content/** as an input of @objectstack/docs#build - #11418

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11264-docs-build-content-inputs
Aug 23, 2026
Merged

fix(build): declare content/** as an input of @objectstack/docs#build#11418
os-steve merged 1 commit into
mainfrom
claude/issue-11264-docs-build-content-inputs

Conversation

@claude

@claudeclaudeBot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes#11264

What changed

One entry added to the root turbo.json:

"@objectstack/docs#build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "json-schema/**", ".next/**", "!.next/cache/**"],
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/content/**"]
}

The docs site renders 442 files that live under content/, outside apps/docs/.
With no @objectstack/docs#build entry the task inherited the generic build
task, whose inputs are $TURBO_DEFAULT$ — the package's own files only. A
content-only commit therefore hashed identically to its predecessor, so
pnpm turbo run build --filter=@objectstack/docs (what apps/docs/vercel.json
builds production with) could replay a stale .next against new content.

Acceptance — the card's own probe, at 6f13167 on a clean tree

Append one line to content/docs/index.mdx, re-derive the hash, revert.
Mutation confirmed on disk each run (marker present, 3892 -> 3938 bytes);
restore confirmed (marker absent, back to 3892).

declared inputsunder content/hash before edithash after editverdict
before fix (bb00644)350a19d5ea9a91fe215a19d5ea9a91fe215immobile
after fix (6f13167)4774427ff31b1218531d71b052c8482715af13moves

477 = 35 + 442, which is what shows $TURBO_DEFAULT$ was added to, not
replaced: had the entry dropped it the count would read 442. 442 is exactly
find content -type f | wc -l.

Which tasks' behaviour is verified unchanged

turbo.json is a build-graph file, so the blast radius was measured rather
than argued. Every task graph was dry-run with turbo.json at origin/main
and with this change, comparing per-task hash, input count and
resolvedTaskDefinition:

task graphtasks compareddiffering
build78@objectstack/docs#build only
test144none — byte-identical
typecheck144none — byte-identical

And within @objectstack/docs#build itself, exactly one field of the resolved
definition moves:

SAME cache: true SAME dependsOn: ["^build"]
SAME outputs: ["!.next/cache/**", ".next/**", "dist/**", "json-schema/**"]
SAME env / passThroughEnv / persistent / interactive / interruptible / outputLogs
DIFF inputs: [] -> ["../../content/**"]

dependsOn and outputs were copied from the inherited definition rather
than from the card's suggestion, which proposed outputs: [".next/**", "!.next/cache/**"]. Narrowing outputs would have been a second, unrelated
behaviour change riding along; copying them verbatim makes the input set the
only thing this PR moves.

Same-pass sweep for other tasks with the same gap

The card asks whether any other task consuming content/ has it. Result: no
second fix is warranted, and the one near-miss is a measured negative.

  • @objectstack/docs#typecheck — same shape on paper (35 inputs, 0 under
    content/) and it does read content/ (fumadocs-mdx regenerates
    .source/ from it). But its verdict cannot depend on content:
    .source/server.ts is @ts-nocheck. Measured rather than reasoned — ran the
    task with a frontmatter violation already in place (title: 12345 plus a
    bogus key in content/docs/index.mdx) and it passed, exit 0. Adding
    content/** there would invalidate the cache on every docs edit while
    changing no verdict — a cost, not a fix. Deliberately not done.
  • gen:schema / gen:skill-refs — the only other cached non-test tasks;
    neither reads content/. gen:docs, which does write
    content/docs/references/, is not a turbo task at all, so it is uncached.
  • The #test tasks (spec, cli, dogfood, rest, create-objectstack)
    already declare their content/ reads, and check:cross-package-test-inputs --verify enforces that — it passes here, confirming turbo.json still
    hashes every declared glob.
  • The many scripts/check-*.mjs gates that read content/ are not turbo tasks;
    CI invokes them directly, so no cache can stale them.

Gates

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derives
0 matched families for turbo.json. Since this diff is build config, the
workflow step lists were read directly and every gate that actually parses
turbo.json was run at 6f13167:

check:nul-bytes OK (6400 text files, no raw ASCII control bytes)
check:cross-package-test-inputs OK: 14 package(s) read outside themselves, all
declared, and turbo.json hashes every declared glob
check:type-check-coverage OK — 65/78 workspace packages type-checked
check:examples-live-imports OK -- 0 invisible (declared), 6 inputs-declared
check:test-source-alias OK — 72 packages with tests scanned
check:type-source-resolution OK — 77 packages with a tsconfig.json scanned
check:dev-prereqs --self-test ✓ 16 cases (the self-test is the only half
lint.yml runs; the scan half is not a CI gate)

No changeset: @objectstack/docs is private: true and turbo.json is repo
infrastructure, so this PR releases nothing — hence skip-changeset.

Comments were kept out of the file on purpose:
scripts/check-type-check-coverage.mjs:3271 does a strict
JSON.parse(readFileSync('turbo.json')), so JSONC would red that gate.

The half that is still unmeasured — severity, not correctness

Whether Vercel presents a warm Turborepo remote cache for this project is not
observable from a dev container, and this PR does not claim to have measured it.
That reading decides how live the bug is, not whether the declaration was
missing. Per the card: "if remote caching is on for this project, this is live;
if it is off, this is a loaded gun whose trigger is one dashboard toggle."

Either way the task now declares what it reads.

TURBO_FORCE was deliberately not used. #10642 lands it on the CI step to
protect the gate; forcing a rebuild is right for a gate and wrong as a way
to make production correct. That PR and this one close two halves of one
incident — gate honesty there, production correctness here.

Generated by Claude Code


Generated by Claude Code

The docs site renders every one of its 400+ pages from `content/`, which
lives outside `apps/docs/`. With no `@objectstack/docs#build` entry the
task inherited the generic `build` task, whose inputs are `$TURBO_DEFAULT$`
— the package's own files only. A content-only commit therefore hashed
identically to its predecessor, so `pnpm turbo run build --filter=
@objectstack/docs` (the command `apps/docs/vercel.json` builds production
with) could replay a stale `.next` against new content: a green deploy
serving the previous pages, with no error anywhere.
The new entry reproduces the generic task's `dependsOn` and `outputs`
verbatim and adds only `inputs`, so nothing but the input set changes.
`$TURBO_ROOT$/content/**` is the spelling five sibling tasks already use,
and it covers both collections `source.config.ts` reads (`content/docs`
and `content/blog`) plus `content/docs.site.json`.
Not the same remedy as the `TURBO_FORCE` that protects the CI step:
forcing a rebuild is right for a gate and wrong for production. Production
is correct here because the task declares what it actually reads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 23, 2026
@os-steve
os-steve marked this pull request as ready for review August 23, 2026 16:48
@os-steve
os-steve added this pull request to the merge queueAug 23, 2026
Merged via the queue into main with commit cbf8b2cAug 23, 2026
27 checks passed
@os-steve
os-steve deleted the claude/issue-11264-docs-build-content-inputs branch August 23, 2026 17:03
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude