Skip to content

Fix non-root dependency tracking in build watch mode - #64973

Draft
John Favret (johnfav03) wants to merge 3 commits into
microsoft:mainfrom
johnfav03:fix/64424
Draft

John Favret (johnfav03) wants to merge 3 commits into
microsoft:mainfrom
johnfav03:fix/64424

Conversation

@johnfav03

Copy link
Copy Markdown
Contributor

Fixes #64424

Build watch mode relied on configured roots and incremental build-info file tables to identify affected projects. Non-incremental builds lack that same file table, so changes to non-root imported files could be ignored even when a filesystem notification arrived.

Example: If we have "files": ["index.ts"] and index.ts imports ./other.js, editing other.ts should invalidate the project and cause a rebuild even when incremental and composite are disabled.

Copilot AI balanced review requested due to automatic review settings October 26, 2026 20:23
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Oct 26, 2026
@typescript-automation typescript-automation Bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Oct 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The changes require final human review because they are too complex or risky for automated approval.

No open issues 🎉

What changed in this PR

Fixes #64424 by tracking non-root dependencies independently of incremental file tables, so build watch mode can rebuild non-incremental projects when imported files change.

Changes:

  • Capture dependencies, resolution lookups, and symlink identities; persist dependency inventories in build info.
  • Use those inventories for event classification, watch registration, and freshness checks, with shared source-cache invalidation.
  • Add regression tests and update affected build-info baselines.

Validation commands were not run in this review environment.

File Description
tsc/​testdata/​baselines/​reference/​tsc/​declarationEmit/​reports-dts-generation-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​programUpdates/​works-when-noUnusedParameters-changes-to-false.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​programUpdates/​works-correctly-when-project-with-extended-config-is-removed.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​programUpdates/​tsbuildinfo-has-error.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmitOnError/​noEmitOnError.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmitOnError/​noEmitOnError-with-declaration.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmit/​syntax-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmit/​semantic-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmit/​dts-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmit/​dts-errors-without-dts-enabled.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmit/​does-not-go-in-loop-when-watching-when-no-files-are-emitted.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​noEmit/​does-not-go-in-loop-when-watching-when-no-files-are-emitted-with-incremental.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​moduleResolution/​build-mode-watches-package-json-lookups-from-existing-buildinfo.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​moduleResolution/​build-mode-watches-missing-package-json-lookups.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​extends/​configDir-template.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​transitiveReferences/​builds-correctly.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​solution/​does-not-have-empty-files-diagnostic-when-files-is-empty-and-references-are-provided.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​sample/​tsbuildinfo-has-error.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​sample/​builds-correctly-when-project-is-not-composite-or-doesnt-have-any-references.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​roots/​when-two-root-files-are-consecutive.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​without-outDir.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​without-outDir-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​sourcemap.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​sourcemap-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-without-outDir.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-without-outDir-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-with-json-without-rootDir-but-outside-configDirectory.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-with-json-without-rootDir-but-outside-configDirectory-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-with-json-not-in-rootDir.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-with-json-not-in-rootDir-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-only-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-of-json-along-with-other-include.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-of-json-along-with-other-include-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-of-json-along-with-other-include-and-file-name-matches-ts-file.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-of-json-along-with-other-include-and-file-name-matches-ts-file-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-and-files.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​include-and-files-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​importing-json-module-from-project-reference.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​files-containing-json-file.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​resolveJsonModule/​files-containing-json-file-non-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​projectReferenceWithRootDirInParent/​reports-no-error-when-tsbuildinfo-differ.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​projectReferenceWithRootDirInParent/​reports-error-for-same-tsbuildinfo-file.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​projectReferenceWithRootDirInParent/​reports-error-for-same-tsbuildinfo-file-without-incremental.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​projectReferenceWithRootDirInParent/​reports-error-for-same-tsbuildinfo-file-without-incremental-with-tsc.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​projectReferenceWithRootDirInParent/​reports-error-for-same-tsbuildinfo-file-because-no-rootDir-in-the-base.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​programUpdates/​tsbuildinfo-has-fewer-fileInfos-than-fileNames.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​outputPaths/​when-rootDir-is-specified.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​outputPaths/​when-rootDir-is-specified-but-not-all-files-belong-to-rootDir.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​outputPaths/​when-rootDir-is-specified-but-not-all-files-belong-to-rootDir-and-is-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​outputPaths/​when-rootDir-is-not-specified.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​outputPaths/​when-rootDir-is-not-specified-and-is-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmitOnError/​syntax-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmitOnError/​syntax-errors-with-declaration.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmitOnError/​semantic-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmitOnError/​semantic-errors-with-declaration.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmitOnError/​dts-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmitOnError/​dts-errors-with-declaration.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmit/​syntax-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmit/​semantic-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmit/​dts-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmit/​dts-errors-without-dts-enabled.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​noEmit/​dts-errors-with-declaration-enable-changes.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​when-resolution-is-not-shared.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​shared-resolution-should-not-report-error.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​resolves-specifier-in-output-declaration-file-from-referenced-project-correctly.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-preserveSymlinks.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​resolution-from-d.ts-of-referenced-project.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​impliedNodeFormat-differs-between-projects-for-shared-file.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​fileDelete/​deleted-file-without-composite.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​extends/​resolves-the-symlink-path.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​extends/​configDir-template.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmit/​when-ts-file-is-referenced-through-triple-slash-from-another-project.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmit/​when-declaration-file-is-referenced-through-triple-slash-but-uses-no-references.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmit/​reports-dts-generation-errors.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmit/​reports-dts-generation-errors-with-incremental.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​contentMapperOptionDiagnostics/​rebuild-to-report-mapper-option-diagnostics.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​contentMapperIdentity/​content-mapper-identity-change-forces-rebuild.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​configFileErrors/​when-tsconfig-extends-the-missing-file.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​commandLine/​included-tsconfig-json-can-be-imported-as-json-input.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​commandLine/​emitDeclarationOnly-on-commandline-with-declaration.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​commandLine/​emitDeclarationOnly-false-on-commandline-with-declaration.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​tsbuild/​clean/​tsx-with-dts-emit.js Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​fourslash/​state/​findAllRefsSolutionWithDisableReferencedProjectLoadReferencingDefaultProjectDirectly.baseline Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​fourslash/​state/​findAllRefsSolutionReferencingDefaultProjectDirectly.baseline Updated as part of this pull request.
tsc/​testdata/​baselines/​reference/​fourslash/​state/​findAllRefsProjectWithOwnFilesReferencingFileFromReferencedProject.baseline Updated as part of this pull request.
tsc/​internal/​execute/​tsctests/​readablebuildinfo.go Updated as part of this pull request.
tsc/​internal/​execute/​incremental/​snapshottobuildinfo.go Updated as part of this pull request.
tsc/​internal/​execute/​incremental/​buildInfo.go Updated as part of this pull request.
tsc/​internal/​execute/​incremental/​buildinfo_path_test.go Updated as part of this pull request.
tsc/​internal/​execute/​build/​watchDependencies_test.go Updated as part of this pull request.
tsc/​internal/​execute/​build/​host.go Updated as part of this pull request.
tsc/​internal/​execute/​build/​compilerHost.go Updated as part of this pull request.

Review effort: Balanced

@jakebailey

Copy link
Copy Markdown
Member

I had copilot review this and it found this test case with bad behavior:

func TestBuildWatchDependencyDirectoryReplacement(t *testing.T) {
	t.Parallel()
	for _, incremental := range []bool{false, true} {
		t.Run(fmt.Sprintf("incremental=%v", incremental), func(t *testing.T) {
			t.Parallel()
			const pkg = dependencyProject + "node_modules/widget"
			const manifest = `{"name":"widget","types":"index.d.ts"}`
			const output = dependencyProject + "out/index.d.ts"

			files := FileMap{
				dependencyProject + "tsconfig.json": fmt.Sprintf(`{
					"compilerOptions": {
						"outDir": "out",
						"declaration": true,
						"incremental": %v,
						"module": "nodenext",
						"target": "es2022",
						"types": [],
						"libReplacement": false
					},
					"include": ["**/*.ts"]
				}`, incremental),
				dependencyProject + "index.ts": `import { value } from "widget"; export const result = value;`,
				pkg + "/package.json":          manifest,
				pkg + "/index.d.ts":            `export const value: 1;`,
			}
			sys, watcher := startDependencyWatch(
				t, files,
				[]string{"--build", "--watch", "--verbose", "--pretty", "false"},
				false, "",
			)
			checkDependencyOutput(t, sys, output, "result: 1;")

			sys.removeNoError(pkg + "/index.d.ts")
			sys.removeNoError(pkg + "/package.json")
			sys.removeNoError(pkg)
			sys.writeFileNoError(pkg+"/package.json", manifest)
			sys.writeFileNoError(pkg+"/index.d.ts", `export const value: 2;`)

			sys.mockWatchBackend.SendEvents([]fswatch.Event{
				{Kind: fswatch.EventUpdate, Path: pkg},
			})
			watcher.DoCycle()

			checkDependencyOutput(t, sys, output, "result: 2;")
			checkDependencySuccess(t, sys)
		})
	}
}

Both incremental variants leave the output at result: 1;. Removing the task.watchDependencies != nil early return in orchestrator.go makes both pass. Directory delete/recreate events can coalesce into an update without individual child-file notifications.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The intertwined watcher, dependency-tracking, and shared-cache changes require cross-platform build/watch and concurrency validation unavailable in this review environment.

No open issues 🎉

Review effort: Balanced

@jakebailey

Copy link
Copy Markdown
Member

I'm sort of having a hard time trying to review this; is this potentially possible to rebase into a few commits? Or is it definitely that all is required and one single fix?

@jakebailey

Copy link
Copy Markdown
Member

Maybe Andrew Branch (@andrewbranch) can internalize this better than i can though

@johnfav03

Copy link
Copy Markdown
Contributor Author

Jake Bailey (@jakebailey) I split it up into 3 commits, let me know if this is easier to review or if there's something else I can do

@jakebailey Jake Bailey (jakebailey) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#64424 (comment) though maybe this should be drafted?


// NonIncrementalProgram info
SemanticErrors bool `json:"semanticErrors,omitzero"`
// A nil inventory predates dependency tracking; an empty inventory is complete.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like some agent text which encodes a WIP discovery; i'd redo or remove this

continue
}
for eventPath := range normalizedPaths {
if eventPath != inputPath && eventPath.ContainsPath(inputPath) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came from a local Copilot review: I think this misses directory-only events for lookup dependencies such as package.json. A manifest can redirect resolution to a different declaration file without changing either a consumed input or an alias. In that case, an update event for the package directory is ignored and the build output stays stale. Could directory events also invalidate when they cover a tracked lookup?

sys, watcher := startDependencyWatch(t, files, []string{"--build", "--watch", "--verbose", "--pretty", "false"}, startup != "live", "")
checkDependencyOutput(t, sys, output, "result: 1;")
notify := func() {
sys.mockWatchBackend.SendEvents([]fswatch.Event{{Kind: fswatch.EventUpdate, Path: pkg}})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came from a local Copilot review: could we add a regression case where a package manifest redirects from one existing declaration file to another, and the watcher receives only an update for the package directory? That would cover lookup changes that don’t involve replacing a consumed input file.

@andrewbranch

Copy link
Copy Markdown
Member

The previous behavior was intentional in Strada: #64424 (comment)

I think it's worth reconsidering, but this deserves a deep dive into why the old design was the way it was, an analysis of the semantic implications of changing it, a design meeting to agree on the direction and scope of the change, a report of the performance tradeoffs... This is feature work that belongs in the beginning of a release cycle, not a bug fix we should be considering for an RC.

@johnfav03
John Favret (johnfav03) marked this pull request as draft October 29, 2026 20:24

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

tsc -b --watch doesn't rebuild when an imported file changes, unless incremental is on

4 participants