Repository navigation
Description
What
apps/docs/package.json declares two near-identically named type-checking scripts:
"types:check": "fumadocs-mdx && next typegen && tsc --noEmit",
"typecheck": "tsc --noEmit"types:check — the thorough one — is called by nothing. Measured on
origin/main @ 5886ee6d22:
grep -rn "types:check" .github/ scripts/ -> no matches
No workflow, no script, no turbo task references it. The one that actually runs is
typecheck, reached through the root turbo run typecheck.
Why it may be worth a card
apps/docs/tsconfig.json includes the generated route types:
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx",
".next/types/**/*.ts", ".next/dev/types/**/*.ts"]Those paths are produced by next typegen — which only types:check runs. So the
program typecheck actually checks depends on whether some earlier, unrelated
command happened to populate .next: a previous next build or next dev in the
same checkout. A clean checkout type-checks a strictly smaller program than a dirty
one, and neither is declared anywhere.
Measured directly while verifying #10782: with apps/docs/.next deleted,
pnpm --filter @objectstack/docs typecheck -> DOCS_TYPECHECK_EXIT=0
It passes without the generated route types existing at all. That is the
"reads green, verified less" shape — the failure is not that it goes red, but that
its coverage is set by incidental build state and nothing says so.
Two things a triage round might separate:
- the dead
types:checkscript (either wire it up or delete it — ADR-0049's
enforce-or-remove instinct applied to a package script); and - whether the docs app's declared type-check should include
next typegen, so
coverage is a property of the script rather than of leftover.nextstate.
I have not verified whether the generated route types would actually catch anything
in this app today — that is the triage question, not a claim made here.
Found while working on #10782 (Next middleware -> proxy convention rename);
out of that card's file surface, so filed rather than fixed. Unassigned.
Activity
claude commentedon Aug 21, 2026
Triage first-touch + claim, by the
domain:devxPM seat (#6023, sessionsession_01DdCnBGcHeufjrq7drTD3wt).All three measurements re-verified on
origin/mainbefore promoting — this card is dispatchable as filed.types:check=fumadocs-mdx && next typegen && tsc --noEmit;typecheck=tsc --noEmittypes:checkis invoked by nothinggit grep types:checkover.github,scripts,turbo.json, rootpackage.json⇒ zero hitsincludecarries both.next/types/**/*.tsand.next/dev/types/**/*.ts⇒ CI only ever runs the typegen-less half, over a
tsconfigwhoseincludenames two directories that only exist if something previously built. The filer's one-line measurement is the whole finding: with.nextdeleted,typecheckexits 0.⭐ That is this lane's recurring shape once more — a green that depends on a leftover artifact. Same family as the
dist/ablation trap the dispatch template warns about, except here the stale artifact makes the check pass rather than mask a rebuild.Claim:
/pm-dispatchdomain:devx execution seat.Session:
session_01DdCnBGcHeufjrq7drTD3wt· Branch:claude/issue-10871-docs-typecheck-typegen· Worktree:objectstack-issue-10871Container & model:
M,mode:subagent,model: opusFile surface:
apps/docs/package.json,apps/docs/tsconfig.json(if needed), and whichever CI/turbo wiring the fix requires.Scope, and the judgement left to the implementer
The question is not "wire
types:checkinto CI" reflexively — it is: which of the two scripts is the real contract, and does the other survive? Two scripts differing only by typegen, one of them dead, is itself the defect. Measure whether running typegen changes the verdict (doestscwith populated.next/typescatch anything the bare run misses?) and let that decide:types:check, andtypecheckshould stop existing as a trap;types:checkand narrowinginclude, so the config stops promising coverage it does not have.⛔ Do not leave both. ⭐ And whichever way it goes, the deliverable must include the property that the filer measured: a check that cannot pass merely because
.nexthappens to be populated. State how your change makes the empty-.nextcase honest.Standing constraints
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md.content/docs/releases/**. ⛔ Nevergit stash—refs/stashis shared by every worktree.cmd > log 2>&1; ec=$?.pnpm --filter <typo>exits 0 with "No projects matched the filters" — a whole verification round can be fictitious and read as green #10853:pnpm --filter <name>with a name that does not exist printsNo projects matched the filtersand EXITS 0. Verify your filter matched — a sibling proved@objectstack/docsresolves bypnpm --filter @objectstack/docs exec pwd, which is the cheap check./home/user/objectstackis STALE. Measure againstorigin/mainor your own worktree.apps/docsisprivate: truewithprivatePackages.tag=false⇒ likelyskip-changeset; decide from your real file list.mainhasConsole Pin Gatered for a known unrelated cross-repo reason (p0: main is RED — #10695 retired six spec/ui theme schemas that the PINNED objectui still imports, so Console Pin Gate cannot build the SPA #10856) — non-blocking, ⛔ not yours.Report back to me; I review against GitHub and land it.
Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
{ "issue": 10871, "status": "done", "branch": "claude/issue-10871-docs-typecheck-typegen", "pr": "https://github.com/objectstack-ai/objectstack/pull/10879", "premise_still_valid": true, "summary": "All three filed measurements held on origin/main @ 7d483e1e5f. The judgement went to the thorough command: typegen materially changes coverage, so `types:check` is DELETED and its exact command becomes `typecheck` — the name `turbo run typecheck --filter='./apps/*'` and `check:type-check-coverage` already require, so no turbo or CI wiring moves and both scripts are not left standing. The empty-.next case is honest because the check now PRODUCES the generated inputs it reads (fumadocs-mdx writes .source/, next typegen writes .next/types/** and next-env.d.ts) before tsc runs, and `&&` short-circuits loudly if typegen fails — it can no longer pass because someone else populated .next, because it populates .next itself. One half of the card's alternative was FALSIFIED: `include` cannot be narrowed, because Next owns that array and re-adds a deleted glob on the next `next dev`/`next build` (measured by running writeConfigurationDefaults against a narrowed copy). The rationale is recorded as a comment in tsconfig.json where the next reader would try to tidy it; the residual `.next/dev/types/**` can only add a local false RED, never a false green, and Next's own build-mode check filters that dir out for the same reason.", "tests": "All at final commit 8140f71b39, exit codes captured as `cmd > log 2>&1; ec=$?` (never through a pipe). COVERAGE DELTA (empty .next, tsc --noEmit --listFiles): bare = A_EXIT=0, A_PROGRAM_FILES=1225; typegen'd = B_EXIT=0, B_PROGRAM_FILES=1231; DELTA_COUNT=6 = .next/types/{validator.ts,routes.d.ts,root-params.d.ts,cache-life.d.ts}, next-env.d.ts, next/image-types/global.d.ts. Cost: B_TYPEGEN_SECONDS=1, no build needed. ABLATION (extra required key on app/[lang]/docs/layout.tsx params) — no package build or dist is on this resolution path, so no rebuild leg applies; the mutation was confirmed ON DISK by marker counts, not by the editor's exit code: anchor_count 1->0, marker_count 0->1, `1 file changed, 1 insertion(+), 1 deletion(-)`. LEG1_BARE_TSC_EXIT=0 with LEG1_ERROR_LINES=0 (today's CI check, green over a broken route signature); LEG2_TYPEGEN_TSC_EXIT=2 with `.next/types/validator.ts(139,31): error TS2344 ... Property 'nonexistentParam' is missing in type '{ lang: string; }'`. RESTORE leg proven on disk too: anchor_count back to 1, marker_count 0, `git status --porcelain` empty for the file, RESTORED_TYPEGEN_TSC_EXIT=0. CARD'S REQUIRED PROOF (rm -rf .next next-env.d.ts first): `pnpm --filter @objectstack/docs typecheck` -> TYPECHECK_EXIT=0 in 4s, next_types_dir=yes next_env_now=yes; negative control on the same tree OLD_BARE_TSC_EXIT=0. Filter resolution verified per #10853: `pnpm --filter @objectstack/docs exec pwd` printed /home/user/objectstack-issue-10871/apps/docs. CI lane's own command: `turbo run typecheck --filter='./apps/*' --force` -> `Tasks: 2 successful, 2 total`. INCLUDE SELF-REVERT: writeConfigurationDefaults against a narrowed copy printed `include was updated to add '.next/dev/types/**/*.ts'`, DEV_GLOB_READDED_BY_NEXT=YES. GATES derived by `node scripts/pm/dispatch-gates.mjs` (no paths passed; it took 2 paths vs merge base 7d483e1e5, exit 0) and run at 8140f71b39, each quoting its own verdict line: check:override-consistency `✓ 8 published-manifest declaration(s) ... resolve to their override targets.`; check:test-source-alias `check-test-source-alias OK — 72 packages with tests scanned`; check:type-source-resolution `check-type-source-resolution OK — 76 packages with a tsconfig.json scanned`; check-changeset-fixed `✓ .changeset/config.json \"fixed\" group is in sync with 69 public workspace packages.`; check-osv-exemptions `✓ osv-scanner.toml holds zero OSV exemptions (the intended steady state).`; check:nul-bytes `check-nul-bytes: OK (scanned 6294 text file(s) ... no raw ASCII control bytes).`; check:type-check-coverage `check-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root)`. The last two are not path-derived: nul-bytes is the any-edit gate, type-check-coverage reads exactly the two files this diff changes. CI itself is in_progress — reporting at draft-PR time per the standing contract, not waiting for convergence.", "open_questions": [], "out_of_scope_findings": [ "filed as #10880: check:type-check-coverage has no invariant for a tsconfig `include` naming a GENERATED directory the package's typecheck script never generates — the class-closing guard for this card, deliberately kept out of the PR because it would edit the 3790-line required-context gate that #10756 is already dispatched against (same file, different invariant; needs sequencing, not parallel work)" ] }Label read-back (applied additively as the union with what the bots had already written, never a bare set):
documentation,dependencies,size/s,tooling,skip-changeset.skip-changesetis the right call from the real file list — two files,apps/docsisprivate: truewithprivatePackages.tag: false, so nothing user-visible ships and there is no tag or release to describe.PR #10879 is a draft, as dispatched. Diff is
apps/docs/package.json(+1/-2) andapps/docs/tsconfig.json(+24/-0), 2 files.Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
os-dev-report
{ "issue": 10871, "status": "done", "branch": "claude/issue-10871-docs-typecheck-typegen", "pr": "https://github.com/objectstack-ai/objectstack/pull/10879", "premise_still_valid": true, "summary": "All three filed measurements held on origin/main @ 7d483e1e5f. The judgement went to the thorough command: typegen materially changes coverage, so `types:check` is DELETED and its exact command becomes `typecheck` — the name `turbo run typecheck --filter='./apps/*'` and `check:type-check-coverage` already require, so no turbo or CI wiring moves and both scripts are not left standing. The empty-.next case is honest because the check now PRODUCES the generated inputs it reads (fumadocs-mdx writes .source/, next typegen writes .next/types/** and next-env.d.ts) before tsc runs, and `&&` short-circuits loudly if typegen fails — it can no longer pass because someone else populated .next, because it populates .next itself. One half of the card's alternative was FALSIFIED: `include` cannot be narrowed, because Next owns that array and re-adds a deleted glob on the next `next dev`/`next build` (measured by running writeConfigurationDefaults against a narrowed copy). The rationale is recorded as a comment in tsconfig.json where the next reader would try to tidy it; the residual `.next/dev/types/**` can only add a local false RED, never a false green, and Next's own build-mode check filters that dir out for the same reason.", "tests": "All at final commit 8140f71b39, exit codes captured as `cmd > log 2>&1; ec=$?` (never through a pipe). COVERAGE DELTA (empty .next, tsc --noEmit --listFiles): bare = A_EXIT=0, A_PROGRAM_FILES=1225; typegen'd = B_EXIT=0, B_PROGRAM_FILES=1231; DELTA_COUNT=6 = .next/types/{validator.ts,routes.d.ts,root-params.d.ts,cache-life.d.ts}, next-env.d.ts, next/image-types/global.d.ts. Cost: B_TYPEGEN_SECONDS=1, no build needed. ABLATION (extra required key on app/[lang]/docs/layout.tsx params) — no package build or dist is on this resolution path, so no rebuild leg applies; the mutation was confirmed ON DISK by marker counts, not by the editor's exit code: anchor_count 1->0, marker_count 0->1, `1 file changed, 1 insertion(+), 1 deletion(-)`. LEG1_BARE_TSC_EXIT=0 with LEG1_ERROR_LINES=0 (today's CI check, green over a broken route signature); LEG2_TYPEGEN_TSC_EXIT=2 with `.next/types/validator.ts(139,31): error TS2344 ... Property 'nonexistentParam' is missing in type '{ lang: string; }'`. RESTORE leg proven on disk too: anchor_count back to 1, marker_count 0, `git status --porcelain` empty for the file, RESTORED_TYPEGEN_TSC_EXIT=0. CARD'S REQUIRED PROOF (rm -rf .next next-env.d.ts first): `pnpm --filter @objectstack/docs typecheck` -> TYPECHECK_EXIT=0 in 4s, next_types_dir=yes next_env_now=yes; negative control on the same tree OLD_BARE_TSC_EXIT=0. Filter resolution verified per #10853: `pnpm --filter @objectstack/docs exec pwd` printed /home/user/objectstack-issue-10871/apps/docs. CI lane's own command: `turbo run typecheck --filter='./apps/*' --force` -> `Tasks: 2 successful, 2 total`. INCLUDE SELF-REVERT: writeConfigurationDefaults against a narrowed copy printed `include was updated to add '.next/dev/types/**/*.ts'`, DEV_GLOB_READDED_BY_NEXT=YES. GATES derived by `node scripts/pm/dispatch-gates.mjs` (no paths passed; it took 2 paths vs merge base 7d483e1e5, exit 0) and run at 8140f71b39, each quoting its own verdict line: check:override-consistency `✓ 8 published-manifest declaration(s) ... resolve to their override targets.`; check:test-source-alias `check-test-source-alias OK — 72 packages with tests scanned`; check:type-source-resolution `check-type-source-resolution OK — 76 packages with a tsconfig.json scanned`; check-changeset-fixed `✓ .changeset/config.json fixed group is in sync with 69 public workspace packages.`; check-osv-exemptions `✓ osv-scanner.toml holds zero OSV exemptions (the intended steady state).`; check:nul-bytes `check-nul-bytes: OK (scanned 6294 text file(s) ... no raw ASCII control bytes).`; check:type-check-coverage `check-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root)`. The last two are not path-derived: nul-bytes is the any-edit gate, type-check-coverage reads exactly the two files this diff changes. CI itself is in_progress — reporting at draft-PR time per the standing contract, not waiting for convergence.", "open_questions": [], "out_of_scope_findings": [ "filed as #10880: check:type-check-coverage has no invariant for a tsconfig `include` naming a GENERATED directory the package's typecheck script never generates — the class-closing guard for this card, deliberately kept out of the PR because it would edit the 3790-line required-context gate that #10756 is already dispatched against (same file, different invariant; needs sequencing, not parallel work)" ] }Label read-back (applied additively as the union with what the bots had already written, never a bare set):
documentation,dependencies,size/s,tooling,skip-changeset.PR #10879 is a draft, as dispatched. Diff is
apps/docs/package.json(+1/-2) andapps/docs/tsconfig.json(+24/-0), 2 files.Generated by Claude Code