Repository navigation
fix(scripts): an injected tree hashes apart from an installed one, so a local Spec Main Shape Gate reproduction cannot leave verdicts a sibling worktree replays (objectui#12114) - #12125
Merged
objectstack-fleet[bot] merged 3 commits intoOct 11, 2026
Conversation
… a local Spec Main Shape Gate reproduction cannot leave verdicts a sibling worktree replays A linked git worktree shares the main checkout's `.turbo/cache`, and `--force` (`TURBO_FORCE=true`) skips cache reads but still writes. A local reproduction of the gate therefore recorded main-spec type-check verdicts that a sibling worktree's ordinary installed-spec `pnpm type-check` replayed as its own. `turbo.json`'s `globalDependencies` now names the marker `inject` already writes into every replaced spec copy, reached by a literal path through the root link to the spec (turbo does not expand a wildcard inside `node_modules`). An installed tree has no marker and hashes exactly as before; an injected tree hashes apart in both directions, with no flag to remember; a reinstall replaces the copy and the marker with it. CI's `TURBO_FORCE=true` is unchanged. Part of objectui#12114. Claude-Session: https://claude.ai/code/session_01DwLS3LzXyNmyTunMGbpc8W Co-authored-by: Claude <noreply@anthropic.com>
… the objectui#12114 wiring pin fails Claude-Session: https://claude.ai/code/session_01DwLS3LzXyNmyTunMGbpc8W Co-authored-by: Claude <noreply@anthropic.com>
… introduces the turbo hash marker The header said "the marker above" before any line above it named the marker. Claude-Session: https://claude.ai/code/session_01DwLS3LzXyNmyTunMGbpc8W Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-12114-injected-tree-cache
branch
October 11, 2026 07:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12114
Dispatched implementation, PM seat
domain:devxround R83; sessionhttps://claude.ai/code/session_01DwLS3LzXyNmyTunMGbpc8W.What changed
turbo.jsongains"globalDependencies": ["node_modules/@objectstack/spec/.spec-main-shape-gate.json"]: the markerinjectalready writes into every replaced spec copy, reached by a literal path through the repository root's own link to the spec (the root manifest declares@objectstack/spec).scripts/spec-main-shape-gate.mjsexportsTURBO_HASH_MARKER.injectnow logs that turbo will hash the marker, or warns when there is no root link to hash. The header gains the section "...and an injected tree must not leave a verdict an INSTALLED tree replays", and the existing "The turbo cache is a FALSE GREEN here" section is reworded so it stays true.scripts/__tests__/spec-main-shape-gate.test.ts: three new pins (below).scripts/__tests__/turbo-*-inputs.test.tsheaders said "globalDependenciesis unset", which this change makes false. They now say it "holds no source file". These are comment-only edits outside the claim's declared file surface (see Acceptance notes)..github/workflows/spec-main-shape-gate.ymlis not touched. CI still runsTURBO_FORCE=true pnpm type-check, and its existing pin is unchanged.No flag to remember. An installed tree has no marker and hashes exactly as before. An injected tree hashes apart from every installed tree, in both directions. Two injections never share an entry, because the marker carries the sha and the injection time. A reinstall replaces the copy, so the marker goes with it.
Mechanism readings (turbo 2.10.9, from the lockfile)
.turbo/cache(the worktree got none of its own). Every run logsRemote caching disabled, using shared worktree cache.--forcestill writes.turbo run --helpdefines--forceas "Equivalent to--cache=local:w,remote:w". The forced CI-recipe run in the injected worktree wrote entrye02dab71f230fcc0into that shared directory. So addingTURBO_FORCE=trueto a local reproduction protects that run's reads and does nothing about its writes.injectwith main's script: the filtered@object-ui/types#type-checkhashese02dab71f230fcc0both times. On the wholeturbo run type-checkgraph (dry run), 83 of 83 task hashes are equal before and after injecting (46 of 46 type-check tasks).globalDependencies(H4, measured with dry runs).node_modulesIS hashed, although git ignores it, including a path through the root symlink.node_modules(for examplenode_modules/.pnpm/@objectstack+spec@*/node_modules/@objectstack/spec/...) hashes nothing, with no warning. That is why the entry is a literal path, and the root link is what makes a literal path version-free.pnpm install --frozen-lockfilekeeps both the injection and the marker.pnpm install --frozen-lockfile --forcereplaces the copy and removes the marker with it. The hash went back to95eb3fecb9111eb5, the same as the sibling worktree's installed hash.Live two-worktree reproduction, before and after
Setup.
packages/types/srcthat imports a type namedSpecGateProbe12114from@objectstack/spec.injectis the installed published spec plus that one export. Deviation: it is not built from objectstackmain. The thing under test is turbo's cache key, which never reads the spec's bytes, and the probe makes the false verdict visible.turbo run type-check --filter=@object-ui/types.Before (
29b99490, main's script andturbo.json):inject, thenTURBO_FORCE=true(the CI recipe)cache bypass, force executing e02dab71f230fcc0cache hit, replaying logs e02dab71f230fcc0,Cached: 1 cached, 2 total; the replayed log even names worktree A's pathsrc/__gate_probe_12114__.ts(2,15): error TS2305: Module '"@objectstack/spec"' has no exported member 'SpecGateProbe12114'.After (
81dea3ff; the heada1b9bd68differs only by a header comment):95eb3fecb9111eb5; whole graph 83 of 83 equalinjectturbo hashes node_modules/@objectstack/spec/.spec-main-shape-gate.json, ...; hash now67c390de3fefe78f; whole graph 0 of 83 equal to installedcache bypass, force executing 67c390de3fefe78fcache miss, executing 95eb3fecb9111eb5then TS2305,Cached: 0 cached, 2 totalcache hit, replaying logs 67c390de3fefe78fThe full 83-task graph was dry-run, not executed: executing every build would overrun the container's foreground limit. The dry-run counts cover the cache key of every task.
Pins, and proof that they can fail
The new
describeblock, titled "a local reproduction cannot leave a verdict an installed tree replays (objectui#12114)", holds three pins:turbo.jsonnamesTURBO_HASH_MARKER, which isnode_modules/+ spec +MARKER_FILE. The path has no glob characters, the root manifest declares the spec, andnode_modulesis git-ignored.--dry=jsonover a fixture git repository that copies this repository'sturbo.json, with pnpm-shaped relative links and the realinjectCLI. An injection moves every task hash, a second injection moves them again, and a simulated reinstall puts the hashes back exactly.injectprints the warning. The marker inside the virtual store alone is invisible to turbo.Red runs, each made from a committed state with the paths restored from
HEADafterwards (git diff HEADempty, and each path's blob hash equal to HEAD's):turbo.jsonat29b99490, new test file):Tests 3 failed | 28 passed (31).turbo.jsonentry alone, script kept:Tests 2 failed | 29 passed (31). The wiring pin fails on the missing entry. The real-turbo pin fails withexpected [ 'package.json' ] to include 'node_modules/@objectstack/spec/.spec-…'. The firing control stays green.Gates (at
a1b9bd68)pnpm exec vitest runover the 13 test files that readturbo.jsonor the gate script (git grep -l -e turbo.json -e spec-main-shape-gateunderscripts/__tests__)Test Files 13 passed (13),Tests 625 passed (625)pnpm lint:root0 errors; none of the warnings is in a file this PR touchespnpm check:new-line-citations0 new citation(s)pnpm check:control-bytesnode scripts/check-changeset-presence.mjsnode scripts/spec-main-shape-gate.mjs --self-test26/26 passednode scripts/check-governed-queue-guard.mjs --teston the 7 pathsNOT GOVERNEDCost
The
turbo.jsonedit moves the hash of the root task//#type-check:e2e, and every type-check task depends on it. Compared on an installed tree (whole-graph dry run,29b99490against this branch), 0 of 46 type-check hashes carry over and 36 of 36 build hashes carry over. That is a one-time cache miss, the same as anyturbo.jsonedit.Acceptance notes
turbo-*-inputs.test.tscomment lines are outside the claim's declared surface. They were edited because this change would otherwise leave their prose false. No open PR touches them.content/docs/guide/ci-cd-pipeline.md(Spec Main Shape Gate section) and the fenced workflow header still say turbo's hash does not cover the content ofnode_modules. That remains true: the marker is the injection's identity, not the spec's content. Neither one describes the local reproduction recipe, which lives only in the script header. Carrier: none..turbo/cache:e02dab71f230fcc0and67c390de3fefe78f. The first is keyed on a probe file that no longer exists; the second on a unique injection marker. My attempt to delete them was refused by the sandbox as a shared-resource write.Generated by Claude Code