Skip to content

fix(project): release stale retainingProjects entries when program drops a reference#3992

Closed
Zelys-DFKH wants to merge 1 commit into
microsoft:mainfrom
Zelys-DFKH:fix/stale-retaining-projects-3942
Closed

fix(project): release stale retainingProjects entries when program drops a reference#3992
Zelys-DFKH wants to merge 1 commit into
microsoft:mainfrom
Zelys-DFKH:fix/stale-retaining-projects-3942

Conversation

@Zelys-DFKH
Copy link
Copy Markdown

Fixes #3942

Thanks to @DanielRosenwasser for the stack trace, which made tracing the root cause much more direct.

Analysis

updateProgram calls acquireConfigForProject for every project reference it resolves via GetResolvedProjectReference, adding the current project's config path to each referenced config's retainingProjects. When the program is rebuilt with fewer references (a references entry removed from tsconfig), only the new set is acquired. Nothing releases the entries that were in the old program but dropped from the new one.

The dropped config ends up with a stale entry in retainingProjects: the project path is present, but the project no longer treats that config as a reference. The stale entry is invisible during the rebuild because the project is still alive.

The invariant breaks when the project is later deleted. deleteConfiguredProject calls program.RangeResolvedProjectReference to release all references, but that iterates the current program, which does not include the dropped reference. The stale retainingProjects entry survives deletion and persists into the finalized snapshot.

In the next snapshot, if that referenced config changes, configFileRegistryBuilder.DidChangeFiles copies retainingProjects into affectedProjects and passes it to markProjectsAffectedByConfigChanges. That function loads the project path from configuredProjects and panics because the project was already deleted.

The scheduleIdleCacheClean timer is a common trigger. It fires after the project is already gone, carrying accumulated file changes that may include a tsconfig modification.

Fix

After CreateProgram completes, diff the old program's project references against the new program's and release any absent from the new one. This is safer than releasing all old refs eagerly before the rebuild: on incremental updates (single dirty file, same command line), CreateProgram clones the old program via UpdateProgram without calling GetResolvedProjectReference, so the new program's reference set is identical to the old one. An eager release in that path would remove the project from every dependency's retainingProjects, causing config changes in those dependencies to stop marking this project dirty.

The post-rebuild diff catches dropped references regardless of whether the rebuild was full or incremental. References the new program still holds remain untouched. The diff happens outside entry.Locked, matching the existing pattern.

deleteConfiguredProject still releases all references unconditionally on project deletion; the release logic lives in a shared releaseProjectReferences helper. updateProgram uses the post-rebuild diff instead.

The regression test in TestProjectReferencesProgram walks the full sequence: open a file that causes a project reference to be acquired, rebuild after removing the reference, delete the project by closing the file, then change the formerly-referenced config. Without the fix this panics. With the fix it completes cleanly.

PR #3987 addresses the same crash by replacing the panic in markProjectsAffectedByConfigChanges with a guarded skip. That's a reasonable defensive measure. This PR fixes the root cause instead, so retainingProjects stays consistent and the panic legitimately never fires.

Copilot Checklist

I successfully ran these commands at the end of my session, and they completed without error:

  • npx hereby build
  • npx hereby test (TestFindAllRefsDoesNotTryToSearchProjectAfterItsUpdateDoesNotIncludeTheFile and TestDeclarationMapsRename fail with baseline drift; these also fail on origin/main without this change. go test ./internal/project/... passes cleanly.)
  • npx hereby lint
  • npx hereby format

… a program

When updateProgram rebuilds a project's program after a project reference is
removed from tsconfig, the dropped reference's retainingProjects entry was
never released. Later, deleteConfiguredProject only releases the current
program's references, so the stale entry persisted into the next snapshot.
When that referenced config changed, markProjectsAffectedByConfigChanges
panicked because the deleted project appeared in affectedProjects but was no
longer in configuredProjects.

Fix: before rebuilding, release all of the old program's project references.
The new program re-acquires whatever it still needs via GetResolvedProjectReference.

Fixes microsoft#3942

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 19, 2026 19:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes a panic in markProjectsAffectedByConfigChanges caused by stale retainingProjects entries when a project drops a reference during rebuild but is later deleted.

Changes:

  • After CreateProgram, diff old vs new program's resolved project references and release dropped ones from configFileRegistryBuilder.
  • Extracted shared releaseProjectReferences helper; deleteConfiguredProject now uses it.
  • Added regression test simulating reference removal, project deletion, then referenced-config change.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
internal/project/projectcollectionbuilder.go Releases stale project reference retainers post-rebuild and refactors release logic into a helper.
internal/project/projectreferencesprogram_test.go Adds regression test for the panic scenario described in #3942.

@RyanCavanaugh
Copy link
Copy Markdown
Member

Closing automated PRs; please don't run automated coding tools against this repo. We're fully able to do that ourselves.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fatal server crash at markProjectsAffectedByConfigChanges

3 participants