Skip to content

Conversation

@DanielRosenwasser
Copy link
Member

Fixes #2248.

@DanielRosenwasser DanielRosenwasser marked this pull request as ready for review December 6, 2025 17:57
Copilot AI review requested due to automatic review settings December 6, 2025 17:57
Copy link
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

This PR fixes a panic that occurred when WatchEnabled is false but TypingsLocation is set. In this scenario, the typingsWatch field (and other watch fields) remain nil, but ATA can still attempt to clone them, causing a nil pointer dereference when acquiring the mutex.

  • Added nil check to WatchedFiles[T].Clone() to propagate nil instead of panicking
  • Added unit test to verify nil propagation behavior
  • Added integration test to ensure ATA works correctly with WatchEnabled=false

Reviewed changes

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

File Description
internal/project/watch.go Added nil check in Clone method to prevent panic when called on nil receiver
internal/project/watch_test.go Added unit test verifying Clone returns nil when called on nil WatchedFiles
internal/project/ata/ata_test.go Added integration test ensuring ATA doesn't panic with WatchEnabled=false but TypingsLocation set

@jakebailey
Copy link
Member

Where do we end up trying to call clone on nil? I can imagine this fix works, but I'm not sure why we even get that far

@sheetalkamat
Copy link
Member

typingsWatch

In DidUpdateATAState where we get files to watch for ATA and add them for watching

@DanielRosenwasser
Copy link
Member Author

DanielRosenwasser commented Dec 8, 2025

So this would be hard to emulate in VS Code (or it just doesn't happen) because it supports the capability workspace.didChangeWatchedFiles - but it causes issues for scripts written against our server which don't. Basically, when that capability is not set the server creates a session where WatchEnabled is false.

func (s *Server) handleInitialized(ctx context.Context, params *lsproto.InitializedParams) error {
if s.clientCapabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration {
s.watchEnabled = true
}

s.session = project.NewSession(&project.SessionInit{
Options: &project.SessionOptions{
CurrentDirectory: cwd,
DefaultLibraryPath: s.defaultLibraryPath,
TypingsLocation: s.typingsLocation,
PositionEncoding: s.positionEncoding,
WatchEnabled: s.watchEnabled,
LoggingEnabled: true,
DebounceDelay: 500 * time.Millisecond,
PushDiagnosticsEnabled: !disablePushDiagnostics,
Locale: s.locale,
},
FS: s.fs,
Logger: s.logger,
Client: s,
NpmExecutor: s,
ParseCache: s.parseCache,
})

And I believe what happens is that when creating the first project, this conditionally sets typingsWatch (and other watchers).

if builder.sessionOptions.WatchEnabled {
project.programFilesWatch = NewWatchedFiles(
"non-root program files for "+configFileName,
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
core.Identity,
)
project.failedLookupsWatch = NewWatchedFiles(
"failed lookups for "+configFileName,
lsproto.WatchKindCreate,
createResolutionLookupGlobMapper(builder.sessionOptions.CurrentDirectory, builder.sessionOptions.DefaultLibraryPath, project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
)
project.affectingLocationsWatch = NewWatchedFiles(
"affecting locations for "+configFileName,
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
createResolutionLookupGlobMapper(builder.sessionOptions.CurrentDirectory, builder.sessionOptions.DefaultLibraryPath, project.currentDirectory, builder.fs.fs.UseCaseSensitiveFileNames()),
)
if builder.sessionOptions.TypingsLocation != "" {
project.typingsWatch = NewWatchedFiles(
"typings installer files",
lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
core.Identity,
)
}
}

So it's not always there.

@DanielRosenwasser DanielRosenwasser added this pull request to the merge queue Dec 8, 2025
Merged via the queue into main with commit bc8c332 Dec 8, 2025
28 checks passed
@DanielRosenwasser DanielRosenwasser deleted the nilWatchedFilesClone branch December 8, 2025 22:07
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.

Crash from ATA followed by request with no file-watching client capability

4 participants