Fixing TS6305 'Output File Has Not Been Built From Source'
TS6305 is the error TypeScript reports when a project imports code from a referenced project whose outputs are missing or out of date. It is almost always a workflow or configuration mismatch rather than a real type error, which is why it tends to appear right after a fresh clone, a clean, a package move or a switch of branches: the compiler was asked to type-check a project without building its references first, or the references in tsconfig.json do not match how packages actually depend on each other. This guide shows the exact message, the three situations that cause it, and fixes for the command line, CI and editors.
Exact symptoms and error messages
apps/web/src/App.tsx:3:24 - error TS6305: Output file '/repo/packages/ui/dist/index.d.ts' has not been built from source file '/repo/packages/ui/src/index.ts'.
3 import { Button } from '@acme/ui';
~~~~~~~~~~
Editors show the same message as a red squiggle on the import. A related error appears when a referenced project is missing entirely:
error TS6306: Referenced project '/repo/packages/ui' must have setting "composite": true.
And sometimes TS6305 appears on files that do not exist anymore, after a package was renamed or files were moved:
error TS6305: Output file '/repo/packages/ui/dist/legacy/Modal.d.ts' has not been built from source file '/repo/packages/ui/src/legacy/Modal.tsx'.
Root cause analysis
When a project references another, TypeScript resolves imports of that project's source files to the project's outputs. If the output declaration for a source file does not exist, or is older than the source, TypeScript refuses to guess and reports TS6305. The reference model is covered in TypeScript Project References in Monorepos.
Three situations cover nearly every report:
- Plain
tscon a project with references.tsc -p apps/web --noEmittype-checksapps/webbut does not buildpackages/ui. Ifpackages/ui/distdoes not exist (fresh clone, cleaned workspace), TS6305 follows. - References out of sync with dependencies.
apps/webdepends on@acme/uiinpackage.jsonbut does not list../../packages/uiinreferences— or lists it while the build order putswebfirst. TypeScript still knowsuiis a composite project whose outputs it should read, but nothing builds them in the right order. - Stale or inconsistent outputs.
dist/was deleted but.tsbuildinfosurvived elsewhere, sotsc -bthinksuiis up to date and skips it. Or a file was moved and the old output was never cleaned.
Resolution
1. Use build mode. Replace tsc --noEmit and tsc -p in scripts with tsc -b, which builds references first:
{
"scripts": {
"typecheck": "tsc -b",
"typecheck:web": "tsc -b apps/web"
}
}
2. Sync references with dependencies. For each workspace dependency in a package's package.json, add a reference:
// apps/web/tsconfig.json
{
"extends": "@acme/tsconfig/app-bundler.json",
"compilerOptions": { "noEmit": true },
"include": ["src"],
"references": [
{ "path": "../../packages/ui" },
{ "path": "../../packages/utils" }
]
}
Better, generate them, because hand-maintained references drift the moment someone adds a dependency without remembering the second file. A short script (or a tool such as the Nx TypeScript plugin) that reads workspace dependencies and writes references removes the whole class of drift errors. Run it in CI with a check that fails when the generated references differ from the committed ones.
3. Reset stale state. Keep tsBuildInfoFile inside outDir so the two are deleted together, and when in doubt:
npx tsc -b --clean # remove outputs and build info for every project in the graph
npx tsc -b # rebuild from scratch
Editors
Editors run their own language service, not tsc -b. By default, the TypeScript language service redirects referenced projects to their source files, so TS6305 should not appear in the editor even before a build. If it does, one of these is usually true:
disableSourceOfProjectReferenceRedirectis enabled in the consumer'stsconfig.json, which tells the editor to use outputs; then outputs must be built. Remove the option, or runtsc -b --watchin a terminal while you work.- The editor opened the wrong
tsconfig.json— for instance, a file not matched by any project'sinclude, so it falls back to an inferred project with different settings. Check which project the editor uses ("TypeScript: Go to Project Configuration" in VS Code). - The editor's TypeScript version differs from the workspace's; select the workspace version so behaviour matches the command line.
CI
In CI every run starts without outputs, so any typecheck step must build references:
- run: pnpm install --frozen-lockfile
- run: pnpm exec tsc -b
With a task runner, make the typecheck task depend on dependencies' typecheck (or build) and declare the emitted declarations as outputs, so cache hits restore them for dependents:
{
"tasks": {
"typecheck": {
"dependsOn": ["^typecheck"],
"outputs": ["dist/**/*.d.ts", "dist/.tsbuildinfo"]
}
}
}
Without outputs, a cache hit for ui#typecheck replays logs but restores no declarations, and web#typecheck fails with TS6305 on the next machine.
When bundlers and tsc share an output folder
A subtle source of TS6305 in libraries that bundle their JavaScript with tsup, Vite or esbuild while using tsc -b for declarations is the two tools disagreeing about the output folder. The bundler's clean step (clean: true in tsup, emptyOutDir in Vite) deletes dist/, including the declarations and .tsbuildinfo that tsc -b wrote. If build info lives elsewhere — for example, at the package root — tsc -b believes the project is still up to date and does not rewrite the deleted declarations, and every consumer reports TS6305.
Three fixes, in order of preference. Keep tsBuildInfoFile inside dist/ so the bundler's clean also resets incremental state. Or disable the bundler's clean step and let a single clean script remove dist/ before both tools run. Or emit declarations into a separate folder such as dist/types that the bundler never touches, and point the types conditions in exports there. Whichever you choose, run the bundler and tsc -b in a fixed order in the package's build script, and make the task runner's outputs include both sets of files.
Reading the error for renamed or moved packages
After a package is renamed or moved, TS6305 often names paths that look plausible but no longer exist, which sends people searching in the wrong place. The error text always contains two paths: the output TypeScript expected and the source file it mapped from. The source path tells you which referenced project TypeScript thinks owns the file; if that project folder has moved, a consumer's references array still points to the old location. Fix the reference path, then run tsc -b --clean for the whole solution so stale build info for the old location is removed.
Worked example: CI fails, laptops pass
A team's type-check passes on every laptop and fails in CI with TS6305 for @acme/ui. On laptops, packages/ui/dist exists from earlier builds, so plain tsc --noEmit -p apps/web finds outputs. CI starts clean. Two fixes land together: the script becomes tsc -b apps/web, and the Turborepo typecheck task gains dependsOn: ["^typecheck"] and declaration outputs. The team also adds git clean -xdf to a nightly job to catch any future reliance on leftover outputs.
Prevention and guardrails
- Use
tsc -beverywhere references exist — scripts, CI, pre-commit hooks. - Generate and verify references from
package.jsondependencies. - Keep build info inside
outDirso cleaning resets both. - Declare declaration outputs on cached type-check tasks.
Frequently Asked Questions
Can I silence TS6305? There is no flag to ignore it, and you would not want one: it means the compiler cannot see accurate types for a dependency. Build the reference instead.
Why does TS6305 name a file I deleted?
A consumer still imports it, or stale build info still lists it. Search for the import, then run tsc -b --clean and rebuild.
Do I need references if packages point exports at source?
If a package's exports point at src and the consumer does not reference it, TypeScript simply type-checks the source as part of the consumer and TS6305 never appears — at the cost of repeated checking. That is the source-first model described in Using Internal Packages Without a Build Step.
Is TS6305 possible without a references array?
Only in unusual setups where a file belongs to a composite project that TypeScript discovers through module resolution while checking another project. In normal configurations, the error appears only for projects listed in references.
Does deleting node_modules fix it?
Rarely. The problem is missing or stale outputs of your own packages, not installed dependencies. Rebuild the referenced project with tsc -b instead.
Related
- TypeScript Project References in Monorepos explains how references resolve imports.
- Configuring tsc --build with Composite Projects sets up the projects correctly.
- Fixing 'Cannot Find Module' Type Declaration Errors covers related declaration lookup failures.
- Running Workspace Scripts in Topological Order explains ordering outside TypeScript.