You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci: Type Check runs 476 s median on every PR and merge group although its turbo cache is restored — measure the cache hit rate, fix the miss, and type-check affected packages only on PR and merge-queue runs (two-speed CI, objectstack#22998) #12222
Protects: this repository's required Type Check context, the published check-run name every PR and merge group of @object-ui/* passes through; this card keeps the name and cuts its wall.
Filed by the director seat (summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB) on the maintainer's word 「Type Check 的两张后续卡也立吧」 (2026-10-12), the follow-up objectui#12214 names. Filing gate ①: a measured defect with a named landing point. ⛔ Not a claim. Tier H: .github/workflows/**; the PR stays a draft until a GOVERNED_APPROVERS member approves it.
What is measured (29 completed CI runs, 16 pull_request + 8 merge_group + push, the jobs API, 2026-10-12)
reading
value
Type Check job wall min / median / p90 / max
2 / 10.3 / 12.4 / 13.4 min
step Run type-check (pnpm type-check)
476 s median, 620 p90, 680 max (82 % of the job)
step Restore Turbo Cache
2 s median (a two-second restore is a small or empty cache)
the other 30 steps (guards, Type-check scripts/, setup)
about 90 s together
The cache keys look right on paper: key: turbo-${{ runner.os }}-type-check-${{ github.sha }}, restore-keys: turbo-${{ runner.os }}-type-check- then turbo-${{ runner.os }}-, saved whenever the exact key missed. turbo.json declares type-check with cache: true, dependsOn: ["^build", "//#type-check:e2e"] and inputs that include $TURBO_ROOT$/scripts/vite-*.ts. Yet the step re-executes for about 8 minutes on every run, so something defeats the replay: a root task (//#type-check:e2e) or a root-level input whose hash changes on every commit, a cache directory that is not .turbo/cache, or pnpm type-check not going through turbo for every package. Which one is this card's first measurement, not its premise.
What to build (one PR, Tier H, draft until approved; lands after objectui#12214's PR, which edits the same file)
Measure the miss: one PR run with turbo run type-check --summarize (or the run summary) read for cache hit / miss per package and the hash inputs of a missed package; name the input or task that invalidates the hash in the PR body.
Fix it so unchanged packages replay from the restored cache (narrow the invalidating input, split the root task out of dependsOn, or point the cache at the directory turbo writes).
Type-check affected-only on pull_request / merge_group: the packages the diff affects and their dependents (--affected against the merge base, or --filter=...[<base>]), the whole workspace on push and the hourly schedule (the doctrine's full battery). Root-level changes (lockfile, tsconfig*.json, turbo.json, package.json) mean everything affected; state the rule in the workflow.
The job keeps its name Type Check; scripts/check-type-check-coverage.mjs and scripts/check-required-check-set.mjs stay green.
Expected: Run type-check from 476 s to about 60–120 s on a typical PR; after objectui#12214, Type Check is the PR's critical path (10.2 min), so the PR wall follows this card down to the Lint job (6 min).
Acceptance
Type Check median under 4 minutes on three consecutive merge groups and three PRs, read from the jobs API and written in the PR body with the before figures above.
The turbo summary of one PR run shows cache hits for packages the diff did not touch.
Claim: PM loop round 1 · the director seat dispatching on the maintainer's standing order, verbatim 「三仓的 ci 优化卡你负责派发」 (this seat's session, 2026-10-12T12:31Z)
Session: session_01LqNgdPZB2Jnr5HpGbaqLeB
Account: os-zhuang
Branch: claude/issue-12222-type-check-cache-affected
Worktree: objectui-issue-12222
Domain: domain:devx
Seat: domain:devx#9
File surface: .github/workflows/ci.yml (the type-check job only: its turbo cache steps, the affected filter and the root-change rule), turbo.json (the type-check task's inputs / dependsOn if the miss is there), scripts/check-type-check-coverage.mjs and scripts/check-required-check-set.mjs with their tests only if a pin must move honestly; ⛔ not the test job of ci.yml (objectui#12208 PR-2), ⛔ not spec-main-shape-gate.yml (objectui#12240) (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (the default judgment tier; objectstack's dispatch-gates --tier --repo objectstack-ai/objectui on the paths answers no path-derived mandate; finding the cache miss is a measurement-and-judgment task). Tier H: .github/workflows/** is a governed surface in this repository; the PR stays a draft until a GOVERNED_APPROVERS member approves it.
Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: none
Ruling-ref: the card (filed on the maintainer's word verbatim 「Type Check 的两张后续卡也立吧」), objectstack-ai/objectstack#22998 (the doctrine), objectstack-ai/objectstack#23017 (the objectstack sibling: a turbo cache that never restores, the same defect shape)
Serial constraints cleared: PR #12232 (objectui#12214, the last PR on ci.yml) is MERGED (21fdeb34); objectui#12240 (claimed in this act) edits spec-main-shape-gate.yml and the required-set pin only; objectui#12208 PR-2 (the test job of ci.yml) is dispatched after this PR lands. Merge origin/main before opening the PR.
{
"issue": 12222,
"status": "done",
"branch": "claude/issue-12222-type-check-cache-affected",
"pr": "#12251",
"session": "session_01LqNgdPZB2Jnr5HpGbaqLeB — the dispatching seat's session (this run is its subagent)",
"premise_still_valid": true,
"summary": "Measured and fixed. The invalidating task is the root task //#type-check:e2e: every package's type-check dependsOn it, it declared no inputs, and a root task hashes every tracked file (8591 on 923504f; 8593 in CI run 1), so every commit moved all 45-46 type-check hashes (local probe: one untracked docs/ file moved 46/46 type-check and 0/36 build hashes; after fcccda1, 0/46). CI run 1 also showed the restore side: the bare turbo-Linux- restore key matched the docs job's cache saved minutes earlier on the same PR ref (actions/cache tries every key on the run's own ref before main), about 2 GB with 321/401 entries unused; by run 4 nothing was restorable at all (run 3's cache, saved 4 min earlier, and any main type-check cache were gone). The real working set is 80 entries / 9.0 MB. PR #12251 (draft, 4 commits): e33ccd3 turbo run summary + Report what the turbo cache replayed notice; fcccda1 root task inputs = its e2e program, plus 8 import-closure files a tsc --listFilesOnly sweep found outside package+dependency closure added to type-check inputs; 95368ae two speeds (scripts/select-type-check-scope.mjs: turbo --filter=...[BASE] on pull_request/merge_group, derived root rule widens to the workspace, fail-open; push/schedule run every package with TURBO_FORCE); bcfabaf restore only the turbo-Linux-type-check- prefix, save only on push/schedule after --prune to what that run used. No CI run on this PR could replay a type-check (run 2 new hashes by construction; run 3 shadowed by another job's cache; run 4 nothing to restore and PR runs no longer save) — CI replay is observable only after landing, once the first push run seeds main's pruned cache; replay is proven locally (@object-ui/types: miss 42.6 s before vs hit 4.1 s after, same docs/ probe).",
"measured": {
"before_card": "Run type-check 476 s median / 620 p90 / 680 max; Type Check job 10.3 min median / 12.4 p90 (29 runs, jobs API, per the card)",
"run1_e33ccd3c": "CI run 38254636135 job 114829419183: Run type-check 356 s, job wall 470 s (7.8 min), Restore 20 s. Notice: build 30/35 replayed, type-check 0/45, uncached //#type-check:e2e (8593 input files), cache 401 entries 1997.8 MB (321 unused), restored turbo-Linux-docs-339344d7 (another job, same PR ref)",
"run2_fcccda14": "CI run 38255726542 job 114833669552: Run type-check 642 s, job wall 798 s (13.3 min), Restore 18 s. Notice: build 8/35, type-check 0/45 (new hashes, cold by construction), root task 44 input files, cache 473 entries 2004.7 MB (393 unused), restored turbo-Linux-type-check-339344d7 (run 1's)",
"run3_95368ae8": "CI run 38257816387 job 114840337613: scope=workspace (root rule: turbo.json, tsconfig.e2e.json in the diff). Run type-check 311 s, job wall 444 s (7.4 min), Restore 25 s. Notice: build 34/35, type-check 0/45, cache 457 entries 2498.7 MB (377 unused), restored turbo-Linux-6fb281d6 (bare-key match on another job's cache; run 2's type-check entry not restored)",
"run4_bcfabaf2": "CI run 38258825685 job 114844174815: Run type-check 676 s, job wall 797 s (13.3 min), Restore 1 s. Notice: build 0/35, type-check 0/45, cache 80 entries 9.0 MB (0 unused), restored nothing (cold); Prune and Save skipped (pull_request), as designed",
"invalidating_input": "//#type-check:e2e (root task, cache:false, no inputs => every tracked file) folded into every package type-check hash via dependsOn",
"hypotheses": "H1 path/save fine, restore returns another job's cache (bare key, own ref searched first) or nothing — remedied in bcfabaf; H2 confirmed (root task); H3 refuted as cause (build hashes stable; builds replay 30/35 and 34/35 when a cache holds them); H4 implemented, root-only change selects 0 packages (measured) so guarded by the derived root rule; H5 refuted (coverage guard reads package scripts, unchanged and green)",
"head_checks": "All check runs on bcfabaf completed success (Type Check, Test aggregate + 12 shards, Lint, Repo Gates, Build & E2E, Build Docs, Changeset Declaration, Governed Surface Queue Guard, Line Citation Gate, Spec Main Shape Gate) as of 14:08Z"
},
"tests": "At HEAD bcfabaf: pnpm exec vitest run over every test file reading ci.yml / turbo.json / tsconfig.e2e.json plus the two new ones — Test Files 65 passed (65), Tests 2285 passed (2285); pnpm type-check:scripts exit 0; eslint --no-inline-config over the 8 changed script/test files exit 0. Local turbo proof (shared 4-core box): @object-ui/types type-check before fix: same tree hit 3.1 s, + untracked docs/ file miss 42.6 s (root hash fbb2d654 -> 956fc4f3); after fix: seed miss 53.2 s, + docs/ file hit 4.1 s (root hash 4804aa2f unchanged); TURBO_FORCE=true writes an entry the next TURBO_FORCE=false run replays (389474f8). Ablation (after committing fcccda1, via objectstack scripts/ablation-replace.mjs with trap restore): deleting the root task inputs -> e2e-type-check.test.ts 1 failed | 14 passed (the new pin), restored blob == HEAD 02add335, git diff HEAD empty; first attempt was a no-op (helper refused an empty replacement before running the test), not counted. Single-package local turbo only; full pnpm type-check measured by CI (4 runs above). NOT MEASURED: actionlint (absent), repo-wide pnpm lint locally (CI Lint green).",
"gates": "objectstack dispatch-gates.mjs refuses a sister repo by design (exit 2, REFUSING objectstack-ai/objectui), so hand-derived from objectui package.json + workflows, all exit 0 at bcfabaf: check-control-bytes, check-action-ref-convention, check-shell-escape-residue, check-doc-links, check-doc-example-ids, check-doc-component-types, check:new-line-citations, check-type-check-coverage, check-required-check-set --self-test, check:pre-install-import-graph, check:bash32-floor, check:test-path-roots, lint:coverage, check-changeset-presence (no changeset owed), check:entry-guard; ci.yml YAML parse ok; --ran reconciliation NOT MEASURED (script refuses this repo). --tier --repo objectstack-ai/objectui: no path-derived mandate.",
"files_changed": [
".github/workflows/ci.yml (type-check job only: Restore keys, Decide which packages to type-check, Run type-check, Report, Prune, Save if)",
"turbo.json (type-check inputs +8 import-closure files; //#type-check:e2e inputs)",
"tsconfig.e2e.json (comment)",
"content/docs/guide/ci-cd-pipeline.md (type-check row + two-speeds paragraph)",
"scripts/report-turbo-cache.mjs (new)",
"scripts/select-type-check-scope.mjs (new)",
"scripts/tests/report-turbo-cache.test.ts (new)",
"scripts/tests/select-type-check-scope.test.ts (new)",
"scripts/tests/e2e-type-check.test.ts (new pin)",
"scripts/tests/turbo-type-check-inputs.test.ts (INPUTS_NOT_DERIVABLE entries)",
"scripts/tests/vitest-setup-type-check.test.ts and vitest-config-type-check.test.ts (step locator moved honestly)"
],
"deviations": [
"Per-commit figures are NOT in the PR body: the dispatch asked for them there, os-dev.md says the dev writes the PR body once and never PATCHes; os-dev.md wins. Seat amendment for #12251 body: (1) "Three commits" -> "Four commits" and add row bcfabaf2 = restore only turbo-Linux-type-check-, Save on push/schedule only, new step Prune the turbo cache to what this run used; (2) fill the Figures table from measured run1..run4; (3) replace the H1 sentence with the run-1 finding (bare key matched the docs job cache on the PR ref, own ref searched before main; run 4 restored nothing); (4) Two speeds: add "pull-request and queue runs restore and never save".",
"Four commits, not the suggested three: bcfabaf was added after run 1 read the restore side; it was amended twice locally before its first push (no force-push ever).",
"File surface wider than the claim's literal list (ci.yml type-check job, turbo.json, the two check scripts): two new scripts + tests, tsconfig.e2e.json comment, the CI/CD guide row (AGENTS #2 + the doc command-parity pin), e2e-type-check / turbo-type-check-inputs pins, and two older pins (vitest-setup/vitest-config type-check) that located the step by the literal run: pnpm type-check. The ci.yml test job and spec-main-shape-gate.yml untouched.",
"First git push of the empty branch answered remote: fatal error in commit_refs (not 403); succeeded on the first retry.",
"Pushing each commit cancelled the previous CI run's still-running Test shards (workflow concurrency); the Type Check job had completed in each before the next push."
],
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12251/assignees; comment = POST /repos//issues/12222/comments (this report). label-write: 0 (objectui labels are path-driven; labeler applied documentation, configuration, ci/cd, tests). git push: 5 (empty branch + 4 commits; not REST).",
"open_questions": [
{
"question": "Every package type-check still inherits the e2e root task hash, and turbo force-adds the root package.json and turbo.json to a root task's inputs (a !package.json negation is ignored, measured), so a PR touching the root package.json (23 of the last 300 main commits), e2e/ (16) or turbo.json (4) still re-checks every package cold. Decouple?",
"options": [
"A keep as landed: root task inputs = its e2e program; cost above, no contract change",
"B take //#type-check:e2e out of type-check dependsOn and run pnpm type-check:e2e as its own ci.yml step (like type-check:scripts), root type-check script runs both; removes the coupling, but changes what pnpm type-check runs and moves objectui#4471 pins (e2e-type-check.test.ts)"
],
"recommendation": "A now; decide B after landing if the after-landing medians show root-package.json PRs dominating the slow tail"
},
{
"question": "The card files .github/workflows/** as Tier H here, but node scripts/check-governed-queue-guard.mjs --test .github/workflows/ci.yml turbo.json answers NOT GOVERNED in objectui. Which is authoritative for #12251?",
"options": [
"A the guard (ordinary PR; seat lands after CI)",
"B the card (wait for a GOVERNED_APPROVERS approval)"
],
"recommendation": "The PR stays a draft either way (dispatched PR); the seat decides the landing route — flagged, not chosen here"
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in PR #12251 Acceptance notes, not filed — lint has the same coupling: uncached root task //#lint:root (default inputs = every tracked file) is in lint dependsOn; turbo run lint --dry=json before/after one untracked docs/ file moves 46/46 lint hashes, so the Lint job's turbo cache can never replay · dedupe: lint:root turbo hash, lint cache never replays, root task default inputs",
"carrier: 承接者:无 · noted, not filed — other .turbo/cache jobs (ci.yml docs job: restore-keys turbo-Linux-docs- then bare turbo-Linux-; lint.yml; the e2e job) keep the bare turbo-${{ runner.os }}- fallback and save multi-GB caches on every run; measured here as the source of the shadowing (run 1, run 3) and the likely eviction pressure (run 4) · dedupe: turbo cache bare restore key, actions cache eviction, multi-GB turbo cache",
"carrier: 承接者:无 · noted in turbo-type-check-inputs.test.ts and the PR body — that guard derives config root files only; with replay live, an import-closure edge outside a package and its dependency closure is unhashed unless listed (8 listed from a one-off tsc --listFilesOnly sweep, run without dist/); a derived import-closure guard would keep the list honest · dedupe: type-check inputs import closure, INPUTS_NOT_DERIVABLE, listFilesOnly"
]
}
Review: ACCEPT · PR #12251 (draft, head bcfabaf2) · director seat dispatching on the maintainer's order, 2026-10-12T14:20Z
Checked on GitHub, not on the report's say-so:
PR shape: exists, draft, base main (923504f4), body line 1 Fixes #12222 (the only closing keyword), assignee os-zhuang; 12 files, +1,221/−13, four commits pushed one at a time so each CI run measured one (e33ccd3c measure, fcccda14 the fix, 95368ae8 the two speeds, bcfabaf2 restore and save); labels path-driven. The body was amended by this seat from the report's four named spots (one body edit, read back identical). No changeset owed (check-changeset-presence exit 0).
The miss, named and proven: the root task //#type-check:e2e declared no inputs, a root task hashes every tracked file (8,591–8,593), and every package's type-checkdependsOn it, so every commit moved all 45–46 type-check hashes. Local probe: one untracked docs/ file moved 46/46 type-check hashes and 0/36 build hashes before fcccda14, 0/46 after; @object-ui/types missed in 42.6 s before and hit in 4.1 s after on the same probe. H2 confirmed; H3 (build misses) refuted as a cause; H5 refuted (the coverage guard reads package scripts, unchanged and green). H1's restore side, found on the PR's own runs: the bare turbo-Linux- fallback matched another job's 2 GB cache on the PR's own ref (searched before main), 321 of 401 entries unused; by run 4 nothing could be restored at all. Fixed in bcfabaf2: restore only the turbo-Linux-type-check- prefix, save on push / schedule only after pruning to what the run used (80 entries, 9.0 MB).
Diff read:turbo.json (+11/−2): the root task's inputs are its e2e program; eight import-closure files found by a tsc --listFilesOnly sweep join the type-check inputs. ci.yml, the type-check job only (+149/−2): the restore keys, Decide which packages to type-check (scripts/select-type-check-scope.mjs: --filter=...[BASE] on pull_request / merge_group, the derived root rule widening to the whole workspace, any selector failure → the workspace; push / schedule forced over every package), Report what the turbo cache replayed (scripts/report-turbo-cache.mjs, a notice), Prune, Save if. Two new scripts with tests (217 + 193 lines), the e2e pin (e2e-type-check.test.ts, +52), turbo-type-check-inputs.test.ts (INPUTS_NOT_DERIVABLE entries), two older pins that located the step by run: pnpm type-check moved honestly, the pipeline guide's row and paragraph, a tsconfig.e2e.json comment. The test job and spec-main-shape-gate.yml untouched (objectui#12208 and ci(spec-main-shape-gate): drop the merge_group trigger once the ruleset reads INTACT on seven — an advisory verdict on a queue build is read by nobody (objectui#12240 follow-up, letter B) #12252 own them).
Checks on the head bcfabaf2: all 30 check runs success (Type Check, the Test aggregate and 12 shards, Lint, Repo Gates, Build & E2E, Build Docs, Changeset Declaration, Governed Surface Queue Guard, Line Citation Gate, Spec Main Shape Gate). The dev's local union, exits quoted: vitest over every test file that reads ci.yml / turbo.json / tsconfig.e2e.json plus the two new ones 65 files / 2,285 tests; type-check:scripts exit 0; eslint over the 8 changed script/test files exit 0; fifteen check:* gates exit 0 (the list hand-derived: dispatch-gates refuses objectui by design); an ablation of the root task's inputs reds the new pin (1 failed / 14 passed) and is restored by blob hash; NOT MEASURED locally: actionlint (CI's YAML parse and the green workflow stand in), repo-wide pnpm lint (CI Lint green).
Measured on the PR's four runs:Run type-check 356 / 642 / 311 / 676 s against 476 s median before; none replayed a type-check (run 2 cold by construction, run 3 shadowed by another job's cache, run 4 with nothing to restore because PR runs no longer save): the CI replay is first visible after landing, once the first push run seeds main's pruned cache; the card's acceptance (three merge groups and three PRs under 4 minutes) is this seat's reading after landing.
The dev's two questions: 1 → A (keep the root task coupled with its own inputs; a PR touching the root package.json, e2e/ or turbo.json still re-checks every package cold: 23 + 16 + 4 of the last 300 main commits; B, decoupling //#type-check:e2e from type-check, becomes a card only if the after-landing medians show those PRs dominating the slow tail). 2 → the route is the card's: a draft until a GOVERNED_APPROVERS approval (the round's posture for workflows; objectui#12255 is queued for the table row), and, under the two-speed clause, this lane's PRs do not arm the queue while the Test (coverage) main-red (objectui#12256) stands: this PR lands after objectui#12208 PR-2's first commit lands, or with it.
Deviations accepted: the file surface beyond the claim's literal list (each a pin, a guide row or a test the change made false; the test job and the spec gate untouched); four commits instead of three; the body written once and amended by the seat; the first push's remote: fatal error in commit_refs (one retry). Writes: 2 relay strokes (pr_create with assignee; the report 6126065113); mcp_calls 0; no label writes.
Out of scope, carried (carrier none, noted):lint has the same coupling (//#lint:root with default inputs in lint's dependsOn: 46/46 lint hashes move on an untracked docs/ file, so the Lint job's cache never replays): a card for the lane once this lands shows the pattern; the other jobs that use .turbo/cache (docs, lint.yml, e2e) keep the bare fallback and save multi-GB caches every run (the shadowing and eviction measured here); the turbo-type-check-inputs guard derives config root files only, so an import-closure edge outside a package is unhashed unless listed (8 listed).
Landing: the round's posture: the maintainer approves; the seat arms the queue, after the main-red fix per the two-speed clause. This card stays pm:dispatched until the PR lands; the landing record and the six-run reading follow.
LANDED — PR #12251 is MERGED (2026-10-12T14:41Z, approved, readied and queued by the maintainer's own hand while the Test (coverage) main-red objectui#12256 still stands: the maintainer's call over the two-speed clause, recorded as such; merge commit 1ce8db8d) and read in place on origin/main1ce8db8d. Director seat, summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T14:42Z.
probe on origin/main
want
got
turbo.json: //#type-check:e2e declares its own inputs
present (3 entries)
3 entries
ci.ymltype-check job: restore keys turbo-<os>-type-check-<sha> and the prefix only; steps Decide which packages to type-check, Report what the turbo cache replayed, Prune the turbo cache to what this run used
present
lines 674–676, 708, 813, 838, 880
scripts/select-type-check-scope.mjs, scripts/report-turbo-cache.mjs in the tree
2
2
The card was closed by the PR's Fixes line; pm:dispatched stripped and the assignee released in this act.
Owed on this card (the card's acceptance, six readings):main's first push run after this merge seeds the pruned cache (the Report notice says what it saved); then three merge groups and three PRs: the Run type-check step (476 s median before) and the Type Check job wall (10.3 min median), with the cache notice's replay counts; the 4-minute line decides whether question 1's B (decoupling the e2e root task) becomes a card.
Carriers from the ACCEPT, for the domain:devx @ objectui lane: lint has the same coupling (//#lint:root with default inputs in lint's dependsOn: the Lint job's cache never replays) — a CI card the director proposes to the maintainer; the docs, e2e and lint.yml jobs keep the bare turbo-<os>- fallback and save multi-GB caches every run; the turbo-type-check-inputs guard lists eight import-closure edges from a one-off sweep.
Activity
objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 1 · the director seat dispatching on the maintainer's standing order, verbatim 「三仓的 ci 优化卡你负责派发」 (this seat's session, 2026-10-12T12:31Z)
Session:
session_01LqNgdPZB2Jnr5HpGbaqLeBAccount:
os-zhuangBranch:
claude/issue-12222-type-check-cache-affectedWorktree:
objectui-issue-12222Domain:
domain:devxSeat:
domain:devx#9File surface:
.github/workflows/ci.yml(thetype-checkjob only: its turbo cache steps, the affected filter and the root-change rule),turbo.json(thetype-checktask'sinputs/dependsOnif the miss is there),scripts/check-type-check-coverage.mjsandscripts/check-required-check-set.mjswith their tests only if a pin must move honestly; ⛔ not thetestjob ofci.yml(objectui#12208 PR-2), ⛔ notspec-main-shape-gate.yml(objectui#12240) (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(the default judgment tier; objectstack'sdispatch-gates --tier --repo objectstack-ai/objectuion the paths answers no path-derived mandate; finding the cache miss is a measurement-and-judgment task). Tier H:.github/workflows/**is a governed surface in this repository; the PR stays a draft until aGOVERNED_APPROVERSmember approves it.Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: none
Ruling-ref: the card (filed on the maintainer's word verbatim 「Type Check 的两张后续卡也立吧」), objectstack-ai/objectstack#22998 (the doctrine), objectstack-ai/objectstack#23017 (the objectstack sibling: a turbo cache that never restores, the same defect shape)
Serial constraints cleared: PR #12232 (objectui#12214, the last PR on
ci.yml) is MERGED (21fdeb34); objectui#12240 (claimed in this act) editsspec-main-shape-gate.ymland the required-set pin only; objectui#12208 PR-2 (thetestjob ofci.yml) is dispatched after this PR lands. Mergeorigin/mainbefore opening the PR.objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 12222,
"status": "done",
"branch": "claude/issue-12222-type-check-cache-affected",
"pr": "#12251",
"session": "session_01LqNgdPZB2Jnr5HpGbaqLeB — the dispatching seat's session (this run is its subagent)",
"premise_still_valid": true,
"summary": "Measured and fixed. The invalidating task is the root task
//#type-check:e2e: every package'stype-checkdependsOn it, it declared noinputs, and a root task hashes every tracked file (8591 on 923504f; 8593 in CI run 1), so every commit moved all 45-46 type-check hashes (local probe: one untracked docs/ file moved 46/46 type-check and 0/36 build hashes; after fcccda1, 0/46). CI run 1 also showed the restore side: the bareturbo-Linux-restore key matched thedocsjob's cache saved minutes earlier on the same PR ref (actions/cache tries every key on the run's own ref beforemain), about 2 GB with 321/401 entries unused; by run 4 nothing was restorable at all (run 3's cache, saved 4 min earlier, and anymaintype-check cache were gone). The real working set is 80 entries / 9.0 MB. PR #12251 (draft, 4 commits): e33ccd3 turbo run summary +Report what the turbo cache replayednotice; fcccda1 root task inputs = its e2e program, plus 8 import-closure files atsc --listFilesOnlysweep found outside package+dependency closure added to type-check inputs; 95368ae two speeds (scripts/select-type-check-scope.mjs: turbo--filter=...[BASE]on pull_request/merge_group, derived root rule widens to the workspace, fail-open; push/schedule run every package with TURBO_FORCE); bcfabaf restore only theturbo-Linux-type-check-prefix, save only on push/schedule after--pruneto what that run used. No CI run on this PR could replay a type-check (run 2 new hashes by construction; run 3 shadowed by another job's cache; run 4 nothing to restore and PR runs no longer save) — CI replay is observable only after landing, once the first push run seeds main's pruned cache; replay is proven locally (@object-ui/types: miss 42.6 s before vs hit 4.1 s after, same docs/ probe).","measured": {
"before_card": "Run type-check 476 s median / 620 p90 / 680 max; Type Check job 10.3 min median / 12.4 p90 (29 runs, jobs API, per the card)",
"run1_e33ccd3c": "CI run 38254636135 job 114829419183: Run type-check 356 s, job wall 470 s (7.8 min), Restore 20 s. Notice: build 30/35 replayed, type-check 0/45, uncached //#type-check:e2e (8593 input files), cache 401 entries 1997.8 MB (321 unused), restored turbo-Linux-docs-339344d7 (another job, same PR ref)",
"run2_fcccda14": "CI run 38255726542 job 114833669552: Run type-check 642 s, job wall 798 s (13.3 min), Restore 18 s. Notice: build 8/35, type-check 0/45 (new hashes, cold by construction), root task 44 input files, cache 473 entries 2004.7 MB (393 unused), restored turbo-Linux-type-check-339344d7 (run 1's)",
"run3_95368ae8": "CI run 38257816387 job 114840337613: scope=workspace (root rule: turbo.json, tsconfig.e2e.json in the diff). Run type-check 311 s, job wall 444 s (7.4 min), Restore 25 s. Notice: build 34/35, type-check 0/45, cache 457 entries 2498.7 MB (377 unused), restored turbo-Linux-6fb281d6 (bare-key match on another job's cache; run 2's type-check entry not restored)",
"run4_bcfabaf2": "CI run 38258825685 job 114844174815: Run type-check 676 s, job wall 797 s (13.3 min), Restore 1 s. Notice: build 0/35, type-check 0/45, cache 80 entries 9.0 MB (0 unused), restored nothing (cold); Prune and Save skipped (pull_request), as designed",
"invalidating_input": "
//#type-check:e2e(root task, cache:false, no inputs => every tracked file) folded into every package type-check hash via dependsOn","hypotheses": "H1 path/save fine, restore returns another job's cache (bare key, own ref searched first) or nothing — remedied in bcfabaf; H2 confirmed (root task); H3 refuted as cause (build hashes stable; builds replay 30/35 and 34/35 when a cache holds them); H4 implemented, root-only change selects 0 packages (measured) so guarded by the derived root rule; H5 refuted (coverage guard reads package scripts, unchanged and green)",
"head_checks": "All check runs on bcfabaf completed success (Type Check, Test aggregate + 12 shards, Lint, Repo Gates, Build & E2E, Build Docs, Changeset Declaration, Governed Surface Queue Guard, Line Citation Gate, Spec Main Shape Gate) as of 14:08Z"
},
"tests": "At HEAD bcfabaf:
pnpm exec vitest runover every test file reading ci.yml / turbo.json / tsconfig.e2e.json plus the two new ones — Test Files 65 passed (65), Tests 2285 passed (2285);pnpm type-check:scriptsexit 0; eslint --no-inline-config over the 8 changed script/test files exit 0. Local turbo proof (shared 4-core box): @object-ui/types type-check before fix: same tree hit 3.1 s, + untracked docs/ file miss 42.6 s (root hash fbb2d654 -> 956fc4f3); after fix: seed miss 53.2 s, + docs/ file hit 4.1 s (root hash 4804aa2f unchanged); TURBO_FORCE=true writes an entry the next TURBO_FORCE=false run replays (389474f8). Ablation (after committing fcccda1, via objectstack scripts/ablation-replace.mjs with trap restore): deleting the root task inputs -> e2e-type-check.test.ts 1 failed | 14 passed (the new pin), restored blob == HEAD 02add335, git diff HEAD empty; first attempt was a no-op (helper refused an empty replacement before running the test), not counted. Single-package local turbo only; fullpnpm type-checkmeasured by CI (4 runs above). NOT MEASURED: actionlint (absent), repo-wide pnpm lint locally (CI Lint green).","gates": "objectstack dispatch-gates.mjs refuses a sister repo by design (exit 2, REFUSING objectstack-ai/objectui), so hand-derived from objectui package.json + workflows, all exit 0 at bcfabaf: check-control-bytes, check-action-ref-convention, check-shell-escape-residue, check-doc-links, check-doc-example-ids, check-doc-component-types, check:new-line-citations, check-type-check-coverage, check-required-check-set --self-test, check:pre-install-import-graph, check:bash32-floor, check:test-path-roots, lint:coverage, check-changeset-presence (no changeset owed), check:entry-guard; ci.yml YAML parse ok; --ran reconciliation NOT MEASURED (script refuses this repo).
--tier --repo objectstack-ai/objectui: no path-derived mandate.","files_changed": [
".github/workflows/ci.yml (type-check job only: Restore keys, Decide which packages to type-check, Run type-check, Report, Prune, Save if)",
"turbo.json (type-check inputs +8 import-closure files; //#type-check:e2e inputs)",
"tsconfig.e2e.json (comment)",
"content/docs/guide/ci-cd-pipeline.md (type-check row + two-speeds paragraph)",
"scripts/report-turbo-cache.mjs (new)",
"scripts/select-type-check-scope.mjs (new)",
"scripts/tests/report-turbo-cache.test.ts (new)",
"scripts/tests/select-type-check-scope.test.ts (new)",
"scripts/tests/e2e-type-check.test.ts (new pin)",
"scripts/tests/turbo-type-check-inputs.test.ts (INPUTS_NOT_DERIVABLE entries)",
"scripts/tests/vitest-setup-type-check.test.ts and vitest-config-type-check.test.ts (step locator moved honestly)"
],
"deviations": [
"Per-commit figures are NOT in the PR body: the dispatch asked for them there, os-dev.md says the dev writes the PR body once and never PATCHes; os-dev.md wins. Seat amendment for #12251 body: (1) "Three commits" -> "Four commits" and add row
bcfabaf2= restore onlyturbo-Linux-type-check-, Save on push/schedule only, new stepPrune the turbo cache to what this run used; (2) fill the Figures table frommeasuredrun1..run4; (3) replace the H1 sentence with the run-1 finding (bare key matched the docs job cache on the PR ref, own ref searched before main; run 4 restored nothing); (4) Two speeds: add "pull-request and queue runs restore and never save".","Four commits, not the suggested three: bcfabaf was added after run 1 read the restore side; it was amended twice locally before its first push (no force-push ever).",
"File surface wider than the claim's literal list (ci.yml type-check job, turbo.json, the two check scripts): two new scripts + tests, tsconfig.e2e.json comment, the CI/CD guide row (AGENTS #2 + the doc command-parity pin), e2e-type-check / turbo-type-check-inputs pins, and two older pins (vitest-setup/vitest-config type-check) that located the step by the literal
run: pnpm type-check. The ci.ymltestjob and spec-main-shape-gate.yml untouched.","First
git pushof the empty branch answeredremote: fatal error in commit_refs(not 403); succeeded on the first retry.","Pushing each commit cancelled the previous CI run's still-running Test shards (workflow concurrency); the Type Check job had completed in each before the next push."
],
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12251/assignees; comment = POST /repos//issues/12222/comments (this report). label-write: 0 (objectui labels are path-driven; labeler applied documentation, configuration, ci/cd, tests). git push: 5 (empty branch + 4 commits; not REST).",
"open_questions": [
{
"question": "Every package type-check still inherits the e2e root task hash, and turbo force-adds the root package.json and turbo.json to a root task's inputs (a
!package.jsonnegation is ignored, measured), so a PR touching the root package.json (23 of the last 300 main commits), e2e/ (16) or turbo.json (4) still re-checks every package cold. Decouple?","options": [
"A keep as landed: root task inputs = its e2e program; cost above, no contract change",
"B take //#type-check:e2e out of type-check dependsOn and run
pnpm type-check:e2eas its own ci.yml step (like type-check:scripts), roottype-checkscript runs both; removes the coupling, but changes whatpnpm type-checkruns and moves objectui#4471 pins (e2e-type-check.test.ts)"],
"recommendation": "A now; decide B after landing if the after-landing medians show root-package.json PRs dominating the slow tail"
},
{
"question": "The card files
.github/workflows/**as Tier H here, butnode scripts/check-governed-queue-guard.mjs --test .github/workflows/ci.yml turbo.jsonanswers NOT GOVERNED in objectui. Which is authoritative for #12251?","options": [
"A the guard (ordinary PR; seat lands after CI)",
"B the card (wait for a GOVERNED_APPROVERS approval)"
],
"recommendation": "The PR stays a draft either way (dispatched PR); the seat decides the landing route — flagged, not chosen here"
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted in PR #12251 Acceptance notes, not filed —
linthas the same coupling: uncached root task//#lint:root(default inputs = every tracked file) is inlintdependsOn;turbo run lint --dry=jsonbefore/after one untracked docs/ file moves 46/46 lint hashes, so the Lint job's turbo cache can never replay · dedupe: lint:root turbo hash, lint cache never replays, root task default inputs","carrier: 承接者:无 · noted, not filed — other
.turbo/cachejobs (ci.yml docs job: restore-keysturbo-Linux-docs-then bareturbo-Linux-; lint.yml; the e2e job) keep the bareturbo-${{ runner.os }}-fallback and save multi-GB caches on every run; measured here as the source of the shadowing (run 1, run 3) and the likely eviction pressure (run 4) · dedupe: turbo cache bare restore key, actions cache eviction, multi-GB turbo cache","carrier: 承接者:无 · noted in turbo-type-check-inputs.test.ts and the PR body — that guard derives config root files only; with replay live, an import-closure edge outside a package and its dependency closure is unhashed unless listed (8 listed from a one-off
tsc --listFilesOnlysweep, run without dist/); a derived import-closure guard would keep the list honest · dedupe: type-check inputs import closure, INPUTS_NOT_DERIVABLE, listFilesOnly"]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Review: ACCEPT · PR #12251 (draft, head
bcfabaf2) · director seat dispatching on the maintainer's order, 2026-10-12T14:20ZChecked on GitHub, not on the report's say-so:
main(923504f4), body line 1Fixes #12222(the only closing keyword), assigneeos-zhuang; 12 files, +1,221/−13, four commits pushed one at a time so each CI run measured one (e33ccd3cmeasure,fcccda14the fix,95368ae8the two speeds,bcfabaf2restore and save); labels path-driven. The body was amended by this seat from the report's four named spots (one body edit, read back identical). No changeset owed (check-changeset-presenceexit 0).//#type-check:e2edeclared noinputs, a root task hashes every tracked file (8,591–8,593), and every package'stype-checkdependsOnit, so every commit moved all 45–46 type-check hashes. Local probe: one untrackeddocs/file moved 46/46 type-check hashes and 0/36 build hashes beforefcccda14, 0/46 after;@object-ui/typesmissed in 42.6 s before and hit in 4.1 s after on the same probe. H2 confirmed; H3 (build misses) refuted as a cause; H5 refuted (the coverage guard reads package scripts, unchanged and green). H1's restore side, found on the PR's own runs: the bareturbo-Linux-fallback matched another job's 2 GB cache on the PR's own ref (searched beforemain), 321 of 401 entries unused; by run 4 nothing could be restored at all. Fixed inbcfabaf2: restore only theturbo-Linux-type-check-prefix, save onpush/scheduleonly after pruning to what the run used (80 entries, 9.0 MB).turbo.json(+11/−2): the root task's inputs are its e2e program; eight import-closure files found by atsc --listFilesOnlysweep join thetype-checkinputs.ci.yml, thetype-checkjob only (+149/−2): the restore keys,Decide which packages to type-check(scripts/select-type-check-scope.mjs:--filter=...[BASE]onpull_request/merge_group, the derived root rule widening to the whole workspace, any selector failure → the workspace;push/scheduleforced over every package),Report what the turbo cache replayed(scripts/report-turbo-cache.mjs, a notice),Prune,Save if. Two new scripts with tests (217 + 193 lines), the e2e pin (e2e-type-check.test.ts, +52),turbo-type-check-inputs.test.ts(INPUTS_NOT_DERIVABLEentries), two older pins that located the step byrun: pnpm type-checkmoved honestly, the pipeline guide's row and paragraph, atsconfig.e2e.jsoncomment. Thetestjob andspec-main-shape-gate.ymluntouched (objectui#12208 and ci(spec-main-shape-gate): drop themerge_grouptrigger once the ruleset reads INTACT on seven — an advisory verdict on a queue build is read by nobody (objectui#12240 follow-up, letter B) #12252 own them).bcfabaf2: all 30 check runs success (Type Check, the Test aggregate and 12 shards, Lint, Repo Gates, Build & E2E, Build Docs, Changeset Declaration, Governed Surface Queue Guard, Line Citation Gate, Spec Main Shape Gate). The dev's local union, exits quoted: vitest over every test file that readsci.yml/turbo.json/tsconfig.e2e.jsonplus the two new ones 65 files / 2,285 tests;type-check:scriptsexit 0; eslint over the 8 changed script/test files exit 0; fifteencheck:*gates exit 0 (the list hand-derived:dispatch-gatesrefuses objectui by design); an ablation of the root task's inputs reds the new pin (1 failed / 14 passed) and is restored by blob hash; NOT MEASURED locally: actionlint (CI's YAML parse and the green workflow stand in), repo-widepnpm lint(CI Lint green).Run type-check356 / 642 / 311 / 676 s against 476 s median before; none replayed a type-check (run 2 cold by construction, run 3 shadowed by another job's cache, run 4 with nothing to restore because PR runs no longer save): the CI replay is first visible after landing, once the firstpushrun seedsmain's pruned cache; the card's acceptance (three merge groups and three PRs under 4 minutes) is this seat's reading after landing.package.json,e2e/orturbo.jsonstill re-checks every package cold: 23 + 16 + 4 of the last 300maincommits; B, decoupling//#type-check:e2efromtype-check, becomes a card only if the after-landing medians show those PRs dominating the slow tail). 2 → the route is the card's: a draft until aGOVERNED_APPROVERSapproval (the round's posture for workflows; objectui#12255 is queued for the table row), and, under the two-speed clause, this lane's PRs do not arm the queue while theTest (coverage)main-red (objectui#12256) stands: this PR lands after objectui#12208 PR-2's first commit lands, or with it.testjob and the spec gate untouched); four commits instead of three; the body written once and amended by the seat; the first push'sremote: fatal error in commit_refs(one retry). Writes: 2 relay strokes (pr_create with assignee; the report 6126065113);mcp_calls0; no label writes.linthas the same coupling (//#lint:rootwith default inputs inlint'sdependsOn: 46/46 lint hashes move on an untrackeddocs/file, so the Lint job's cache never replays): a card for the lane once this lands shows the pattern; the other jobs that use.turbo/cache(docs, lint.yml, e2e) keep the bare fallback and save multi-GB caches every run (the shadowing and eviction measured here); theturbo-type-check-inputsguard derives config root files only, so an import-closure edge outside a package is unhashed unless listed (8 listed).Landing: the round's posture: the maintainer approves; the seat arms the queue, after the main-red fix per the two-speed clause. This card stays
pm:dispatcheduntil the PR lands; the landing record and the six-run reading follow.objectstack-fleet commentedon Oct 12, 2026
LANDED — PR #12251 is MERGED (2026-10-12T14:41Z, approved, readied and queued by the maintainer's own hand while the
Test (coverage)main-red objectui#12256 still stands: the maintainer's call over the two-speed clause, recorded as such; merge commit1ce8db8d) and read in place onorigin/main1ce8db8d. Director seat, summon #37,session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T14:42Z.origin/mainturbo.json://#type-check:e2edeclares its owninputsci.ymltype-checkjob: restore keysturbo-<os>-type-check-<sha>and the prefix only; stepsDecide which packages to type-check,Report what the turbo cache replayed,Prune the turbo cache to what this run usedscripts/select-type-check-scope.mjs,scripts/report-turbo-cache.mjsin the treeFixesline;pm:dispatchedstripped and the assignee released in this act.main's firstpushrun after this merge seeds the pruned cache (theReportnotice says what it saved); then three merge groups and three PRs: theRun type-checkstep (476 s median before) and theType Checkjob wall (10.3 min median), with the cache notice's replay counts; the 4-minute line decides whether question 1's B (decoupling the e2e root task) becomes a card.domain:devx@ objectui lane:linthas the same coupling (//#lint:rootwith default inputs inlint'sdependsOn: the Lint job's cache never replays) — a CI card the director proposes to the maintainer; the docs, e2e and lint.yml jobs keep the bareturbo-<os>-fallback and save multi-GB caches every run; theturbo-type-check-inputsguard lists eight import-closure edges from a one-off sweep.Fixes #12222alone.