I reproduced the failure and verified the fix on a minimal monorepo (work in /tmp/race-repro), then wrote the solution to /workspace/solution.md.
Before fix — pnpm exec turbo run build --dry=json:
@mafia/consumer#build deps=[]
@mafia/shared#build deps=[]
Cold --force build → exit 2, error TS2307: Cannot find module '@mafia/shared/types'.
After adding "@mafia/shared": "workspace:*" + pnpm install:
@mafia/consumer#build deps=["@mafia/shared#build"]
@mafia/shared#build deps=[]
Cold pnpm run build (exact CI command, dist + turbo caches removed) → exit 0, shared emitted before consumer.
The full document:
pnpm run build fails with Cannot find module '@mafia/shared/types'pnpm run build (root script → turbo run build) on a cold checkout and fails with:
@mafia/consumer:build: src/index.ts(1,26): error TS2307:
Cannot find module '@mafia/shared/types' or its corresponding type declarations.packages/shared/dist already exists and/or the turbo cache is warm.The consuming package compiles against a sibling workspace package's build output via TypeScript paths, but does not declare that package as a dependency:
// packages/consumer/tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@mafia/shared/types": ["../shared/dist/types"],
"@mafia/shared": ["../shared/dist/index"]
}
}
}
// packages/consumer/package.json (BUG)
{
"name": "@mafia/consumer",
"scripts": { "build": "tsc -p tsconfig.json" }
// ❌ no "@mafia/shared" dependency
}
Turbo builds its task graph from the package dependency graph (package.json), not from tsconfig.json paths. With no dependency, turbo schedules @mafia/shared#build and @mafia/consumer#build in parallel. On a cold machine packages/shared/dist doesn't exist yet, so the consumer's tsc resolves the alias to a nonexistent .d.ts → TS2307. Locally stale dist/ or a warm turbo cache masks the race. Turbo's ^build only enforces ordering when the dependency is declared.
Declare the workspace package in the consuming package's package.json (workspace:*, mirroring sibling packages), then run pnpm install and commit both files:
// packages/consumer/package.json (FIXED)
{
"name": "@mafia/consumer",
"scripts": { "build": "tsc -p tsconfig.json" },
"dependencies": {
"@mafia/shared": "workspace:*"
}
}
# from the repo root
pnpm install # updates pnpm-lock.yaml (adds the workspace link)
git add packages/consumer/package.json pnpm-lock.yaml
The lockfile gains:
packages/consumer:
dependencies:
'@mafia/shared':
specifier: workspace:*
version: link:../shared
Finding every affected package:
rg -n '"@[^"]+":\s*\[\s*"\.\./[^/]+/dist' --glob 'tsconfig*.json' packages
for f in packages/*/package.json; do echo "== $f"; rg -n 'workspace:' "$f" || echo " (no workspace deps)"; done
Turbo knows the edge
bash
pnpm exec turbo run build --dry=json | node -e '
const g = JSON.parse(require("fs").readFileSync(0, "utf8"));
for (const t of g.tasks) console.log(t.taskId, "deps=" + JSON.stringify(t.dependencies));
'
Before: both deps=[] → After: @mafia/consumer#build deps=["@mafia/shared#build"]
Cold forced build
bash
rm -rf packages/*/dist packages/*/.turbo .turbo node_modules/.cache/turbo
pnpm exec turbo run build --force; echo "exit=$?" # exit=0
Exact CI command, fully cold
bash
rm -rf packages/*/dist packages/*/.turbo .turbo node_modules/.cache/turbo
pnpm run build; echo "exit=$?" # exit=0
dist/ masks the bug. Always delete packages/*/dist, .turbo/packages/*/.turbo, node_modules/.cache/turbo. --force bypasses cache but does not remove stale dist/.tsconfig paths do not create turbo edges — only declared package.json dependencies do.pnpm install --frozen-lockfile in CI fails before the build.composite/references) encode the type ordering too, but the turbo edge still requires the package dependency.# Evidence - Problem class: typescript-monorepo-missing-workspace-dep-build-race - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-26T12:15:27.476Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "CI pnpm run build fails cold (Cannot find module @mafia/shared/types) while local warm builds pass. Cause: package compiles against a workspace package via tsconfig paths -> sibling dist/ but declares NO workspace dependency, so turbo has no ^build edge and the cold build races the dist emit. Fix: declare \"@mafia/shared\": \"workspace:*\" in the consuming package.json (mirror packages that declare it) + pnpm install for the lockfile. Verify: pnpm exec turbo run build --dry=json shows the dependency edge; rm -rf <pkg>/dist && pnpm exec turbo run build --force exits 0 cold; run the CI workflow build command exactly. Pitfall: warm turbo cache / lingering dist/ masks the race locally - always prove with dist removed.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "typescript-monorepo-missing-workspace-dep-build-race", "provider": "openrouter", "solved_at": "2026-09-26T12:15:27.476Z", "version": ""}