Skip to content

fix(devx): generate the debt re-measure tsconfig outside the repository - #8260

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-8218-debt-remeasure-temp-tsconfig
Aug 13, 2026
Merged

fix(devx): generate the debt re-measure tsconfig outside the repository#8260
hotlong merged 2 commits into
mainfrom
claude/issue-8218-debt-remeasure-temp-tsconfig

Conversation

@hotlong

@hotlong hotlong commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #8218

check-type-check-coverage.mjs --re-measure wrote tsconfig.debt-remeasure.json into each ledgered package's own directory — a tracked directory matched by no .gitignore rule — and left it there for the whole of that package's tsc run. The finally that removes it was never the gap. The address was.

What was measured, before choosing a shape

The card offered three shapes: gitignore it, harden the cleanup, or move it out of the tree. The measurements picked the third, and one of them nearly picked something else.

The window is normal-path, not crash-path. With the pre-fix code, a git status --porcelain sampler running alongside a full --re-measure saw ?? packages/plugins/plugin-approvals/tsconfig.debt-remeasure.json at sample 22 of a 233s run. No crash involved — any git add -A in the same worktree during those minutes stages a live generated file. SIGKILLing the run mid-measurement then left the file behind permanently, which is the half a finally cannot cover.

The file is genuinely not ignored. git check-ignore -v on the path exits 1 (no match) for both packages/cli/ and packages/plugins/plugin-approvals/.

Moving it out is possible, but not for free — and the cost is silent. The old docblock argued the config had to be a sibling in the package directory, because include / outDir / rootDir resolve relative to the file that declares them. That argument is half right: paths declared UP the extends chain keep resolving against the file that declared them, so the package's own include needs no help. What does need help is everything this generated file declares itself, plus one thing it does not declare at all — typeRoots, which tsc derives from the directory of the root config file. Measured on packages/cli, same tree, same sources:

generated project measured errors
in the package directory (before) 188
in os.tmpdir(), typeRoots reconstructed 188
in os.tmpdir(), typeRoots left to tsc 1

That 1 is a single error TS2688: Cannot find type definition file for 'node' — and the counter was happy to record it as debt. A 187-error "improvement", handed to the ledger by a broken measurement, which --lower would then have written down. So TS2688 joins TSC_SETUP_ERROR: the next way this breaks is loud rather than free.

The change

measureTestDebt now builds the project through a pure remeasureProject() and writes it into a fresh mkdtempSync(os.tmpdir()) directory it owns and removes. Every path it emits is absolute and rooted at the package: extends, exclude, include, rootDir, and a reconstructed typeRoots (the ancestor walk tsc would have done from the package, stopped at the repo root so the number cannot depend on what is installed above the checkout). Two implicit paths are made explicit, because from a temp directory an implicit path does not fail — it quietly means something else:

  • an omitted include (tsc substitutes **/* against the temp dir) — now always written, unless the extends chain already selects files;
  • an omitted typeRoots — now written, unless the chain names its own.

Six self-test cases pin those shapes, and every case additionally asserts the blanket property the design rests on: no path in the generated object may be relative.

Why no .gitignore line

No code path in the repo writes that name any more, so the rule would be dead the day it lands — and an ignore rule is precisely what would turn a resurrected stray into an invisible one. This script's own tsconfig scan is supposed to see strays in a package directory; that is the hazard its docblock already named.

The sibling survey

The card asked whether this is the only gate writing scratch into a tracked directory. It is — and the instrument was validated before the empty result was trusted.

  • Dynamic: each of the 63 other check:* gates run with a tight-loop git status --porcelain sampler alongside it. 0 dirtied the tree. Positive control: the same harness pointed at the pre-fix gate saw the stray file, so the empty result is a real answer and not a blind instrument.
  • Static (to cover a scratch file too short-lived for a git status round-trip): a write-API census over 83 script files shortlisted 34 with a create-and-delete signature; the 7 that do not use mkdtempSync were read directly. All write to mktemp -d, os.tmpdir(), a gitignored node_modules/.* holder (check-driver-conformance.mjs — an existing precedent for keeping throwaway state where git cannot see it), gitignored build output, or an intentional tracked baseline behind --update.

Verification

  • pnpm check:type-check-debt (the full --re-measure) on the merged tree: OK — 33 ledger entries re-measured in 243.3s, 1969 raw tsc errors total, none above its recorded number. surplus: none. Identical total to the pre-change baseline (1969, 249.1s), and since chore(devx): lower nine type-check DEBT ledger entries to their measurement (#7888) #8225 left every entry sitting exactly at its measurement, "green + surplus: none" is a per-entry oracle: no entry rose (green) and none fell (no surplus). All 33 measure the same as before.
  • The acceptance test the card actually asked forgit status --porcelain sampled during the run, not only after. 74 samples across a 233s --re-measure: empty at every one, and find for tsconfig.debt-*.json empty at every one. Tree clean afterwards.
  • pnpm check:type-check-coverage (structural + self-test): OK, 25 re-measure cases now hold.
  • pnpm check:type-source-resolution: OK. pnpm check:nul-bytes: OK, plus a direct control-byte scan of the changed file. eslint: clean.
  • Gate families re-derived against the actual changed path (node scripts/pm/dispatch-gates.mjs scripts/check-type-check-coverage.mjs): the derivation added nothing beyond the three families named in the dispatch prompt.

No .changeset/ entry: a CI-internal script releases nothing, which is this repo's own textbook skip-changeset case (lint.yml's changeset-family note). No DEBT / TEST_DEBT ledger number is touched. The stale prose near lines 500 and ~1410 is out of scope: #8189 remains open and is not addressed here.


Generated by Claude Code

claude added 2 commits August 13, 2026 00:37
`--re-measure` wrote `tsconfig.debt-remeasure.json` into each ledgered
package's own directory -- a tracked directory, matched by no .gitignore
rule -- and left it there for the duration of that package's tsc run.
Measured across the ledger that is a ~250s window in which any
`git add -A` in the same worktree stages a live generated file; it
already reached one real commit. The `finally` that removes it was never
the gap, the address was.

The project now goes to a fresh os.tmpdir() directory, which means every
path it emits has to be absolute -- including the typeRoots tsc would
otherwise derive from the config's own location. Without them
packages/cli measures 1 instead of 188, so TS2688 now counts as a broken
measurement rather than as one tidy error.

Refs #8218
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 13, 2026 1:03am

Request Review

@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 13, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review August 13, 2026 01:09
@hotlong
hotlong enabled auto-merge August 13, 2026 01:10
@hotlong
hotlong added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit 021d8dd Aug 13, 2026
24 checks passed
@hotlong
hotlong deleted the claude/issue-8218-debt-remeasure-temp-tsconfig branch August 13, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:type-check-debt writes tsconfig.debt-remeasure.json into tracked package dirs and it is not gitignored — a concurrent git add -A commits it

2 participants