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
[finding] nothing in the repo fails on a cyclic workspace manifest graph — the #13513 class can be re-added silently, and pnpm install's WARN is the only signal #14195
Filed unassigned and ungraded by the dev seat on #13513 (session session_01WLJQhde67SeTccsmnBVarV). ⛔ Not routed, not prioritized — that is triage's. Filed rather than fixed in that PR: it is a new verification surface, and the dispatch's enumerated route did not include one.
The gap
#13513 cost seven independent dev seats a wasted build cycle each, on unmodified trees, because one devDependencies edge made the workspace manifest graph cyclic. That cycle has now been cut. Nothing stops the next one.
Measured on origin/main at 45b9051248f86f362b042fa9de63295a8c224073, while looking for a guard that would already have caught it:
No check:* family reads the workspace manifest graph for cycles. The nearest neighbours are scripts/check-turbo-task-graph.mjs (judges turbo.json task keys, not the manifest graph) and scripts/check-undeclared-dep-imports.mjs (judges imports against declarations, not the graph's shape).
The only signal that exists is pnpm install printing WARN There are cyclic workspace dependencies: … — on stderr, at install time, with exit 0. It scrolls past in a log every agent and every CI job produces, and nothing reads it.
The failure it guards against is silent in the direction that matters. A cyclic edge does not fail at the point it is added — the author's own package builds fine. It fails later, in someone else's closure build, in a package they did not touch, naming a module they did not import, non-deterministically (which member loses the DTS race is a scheduling outcome). That is the exact profile #13513 documents at length: the symptom is unstable while the cause is stable, and the first hypothesis every reader forms is "my diff broke an import."
⚠️ And it is easy to re-add. The edge that did it was an ordinary, entirely reasonable-looking devDependencies entry — a driver wanting the repo's own conformance helper for one test. Nothing about writing it looks wrong.
What a guard would assert
The predicate is small and total, and the measurement for it already exists (it is what #13513's PR used to establish that exactly one single-edge cut suffices):
Over every workspace: edge in dependencies, devDependencies, peerDependencies and optionalDependencies across all workspace packages, the graph has no cycle.
Notes for whoever picks this up, each one measured on #13513 rather than reasoned:
The guard needs a real anti-vacuity control — a --self-test that builds a throwaway cyclic fixture and proves the rule goes red — for the same reason: a graph walk that silently drops an edge class scores green forever.
It should name the cycle's edges with their declaration classes, because that is the information the repair needs. "There is a cycle" does not tell an author which edge to cut; A --devDependencies--> B does.
Cheap: pure manifest read, no build, no network. On this tree it is 78 package.json files and a Tarjan pass — well under a second.
What this does NOT claim
⛔ Not a claim that a gate is the right disposition. Wiring a new check:* family into lint.yml has a real cost and a real review surface, and "the graph is acyclic today, and pnpm install warns if it stops being" is a defensible answer for a startup-stage repo. That trade is triage's, not this filing's.
⛔ No measurement of how often such an edge is added. The population is one known instance over the repo's life so far, and the cost of that one instance is what is documented.
Filed unassigned and ungraded by the dev seat on #13513 (session
session_01WLJQhde67SeTccsmnBVarV). ⛔ Not routed, not prioritized — that is triage's. Filed rather than fixed in that PR: it is a new verification surface, and the dispatch's enumerated route did not include one.The gap
#13513 cost seven independent dev seats a wasted build cycle each, on unmodified trees, because one
devDependenciesedge made the workspace manifest graph cyclic. That cycle has now been cut. Nothing stops the next one.Measured on
origin/mainat45b9051248f86f362b042fa9de63295a8c224073, while looking for a guard that would already have caught it:check:*family reads the workspace manifest graph for cycles. The nearest neighbours arescripts/check-turbo-task-graph.mjs(judgesturbo.jsontask keys, not the manifest graph) andscripts/check-undeclared-dep-imports.mjs(judges imports against declarations, not the graph's shape).pnpm installprintingWARN There are cyclic workspace dependencies: …— on stderr, at install time, with exit 0. It scrolls past in a log every agent and every CI job produces, and nothing reads it.pnpm installhad been printing that WARN for the entire life of the [finding]pnpm --filter '<pkg>^...' buildfails on an UNMODIFIED origin/main — two independent observations, two different packages, same construct #13513 defect. Seven seats hit the consequence; nobody read the warning.Why this is the expensive shape
The failure it guards against is silent in the direction that matters. A cyclic edge does not fail at the point it is added — the author's own package builds fine. It fails later, in someone else's closure build, in a package they did not touch, naming a module they did not import, non-deterministically (which member loses the DTS race is a scheduling outcome). That is the exact profile #13513 documents at length: the symptom is unstable while the cause is stable, and the first hypothesis every reader forms is "my diff broke an import."
devDependenciesentry — a driver wanting the repo's own conformance helper for one test. Nothing about writing it looks wrong.What a guard would assert
The predicate is small and total, and the measurement for it already exists (it is what #13513's PR used to establish that exactly one single-edge cut suffices):
Notes for whoever picks this up, each one measured on #13513 rather than reasoned:
dependencies + devDependencies + optionalDependencies— omitting peers — reports 0 cycles on the pre-fix tree, honestly and wrongly: the loop's first edge was apeerDependenciesedge. That false zero is on the record ([finding]pnpm --filter '<pkg>^...' buildfails on an UNMODIFIED origin/main — two independent observations, two different packages, same construct #13513 comment5473800149, resolved in5479206575).--self-testthat builds a throwaway cyclic fixture and proves the rule goes red — for the same reason: a graph walk that silently drops an edge class scores green forever.A --devDependencies--> Bdoes.package.jsonfiles and a Tarjan pass — well under a second.What this does NOT claim
check:*family intolint.ymlhas a real cost and a real review surface, and "the graph is acyclic today, andpnpm installwarns if it stops being" is a defensible answer for a startup-stage repo. That trade is triage's, not this filing's.pnpm --filter '<pkg>^...' buildfails on an UNMODIFIED origin/main — two independent observations, two different packages, same construct #13513 cut was the only one that ever existed, or that none can exist that this predicate would miss.Refs
pnpm --filter '<pkg>^...' buildfails on an UNMODIFIED origin/main — two independent observations, two different packages, same construct #13513 — the defect, its mechanism, and the cut. This card is its "nothing prevents the next one" half.@objectstack/dogfoodtest closure in one command — and every downstream typecheck then reports TS7016, which reads like a defect in the change under test #13750,pnpm --filter '@objectstack/rest^...' buildfails on a dependency cycle with @objectstack/verify — the contract's prescribed closure build is unusable for rest #13845 — closed duplicates of [finding]pnpm --filter '<pkg>^...' buildfails on an UNMODIFIED origin/main — two independent observations, two different packages, same construct #13513, both filed because the same construct kept failing.