Skip to content

Fall back to inotify when filesystem has errors in fanotify (fix watch in Docker) - #4661

Open
johnfav03 wants to merge 3 commits into
microsoft:mainfrom
johnfav03:johnfav03/fix-docker-watch
Open

Fall back to inotify when filesystem has errors in fanotify (fix watch in Docker)#4661
johnfav03 wants to merge 3 commits into
microsoft:mainfrom
johnfav03:johnfav03/fix-docker-watch

Conversation

@johnfav03

@johnfav03 johnfav03 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes microsoft/TypeScript#63646
Fixes microsoft/TypeScript#63678

Some filesystems, notably Docker bind mounts on macOS and Docker containers' root filesystems, don't implement name_to_handle_at. This is an issue because we default to fanotify for Linux environments, but in these Docker filesystems every fanotify_mark fails with EOPNOTSUPP, so none of the directory watches are ever registered. This results in the initial build succeeding, but subsequent file changes not being detected.

In the fix, when the backend defaults to fanotify and ReconcileWatches fails with ErrFilesystemUnspported, WatchManager closes any existing watches, switches to an inotify backend, and re-registers the desired watch set.

Copilot AI review requested due to automatic review settings July 16, 2026 21:38

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.

Pull request overview

Adds automatic inotify fallback when fanotify cannot watch Docker-backed filesystems.

Changes:

  • Introduces an unsupported-filesystem sentinel error.
  • Tags relevant fanotify failures and switches the watch manager to inotify.
  • Adds focused fallback and error-propagation tests.

Reviewed changes

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

Show a summary per file
File Description
internal/fswatch/watcher.go Defines the filesystem-support error.
internal/fswatch/fanotify_linux.go Classifies unsupported filesystem errors.
internal/fswatch/fanotify_linux_test.go Tests error classification and propagation.
internal/execute/watchmanager/watchmanager.go Implements backend fallback and watch re-registration.
internal/execute/watchmanager/watchmanager_fallback_test.go Tests fallback behavior and production wiring.

Comment thread internal/fswatch/fanotify_linux.go
@johnfav03
johnfav03 requested a review from jakebailey July 27, 2026 21:09
@jakebailey

Copy link
Copy Markdown
Member

I think this approach is probably okay, though I do wonder if the fanotify watch backend should just internally fall back to inotify...

@johnfav03

Copy link
Copy Markdown
Contributor Author

I think this approach is probably okay, though I do wonder if the fanotify watch backend should just internally fall back to inotify...

I just made a change embedding the inotify fallback into the fanotify backend directly, let me know if this is what you had in mind

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.

Pull request overview

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

Suppressed comments (2)

internal/fswatch/watcher.go:266

  • This contradicts the PR description's lifecycle claim: the implementation keeps existing fanotify watches and routes only unsupported requests to inotify; it does not have WatchManager close all watches and switch backends. Please update the description (or implement the stated global switch) so the documented behavior matches the code.
func (w *fallbackWatcher) WatchDirectories(requests []WatchDirectoryRequest) ([]Watch, error) {
	watches, err := w.primary.WatchDirectories(requests)
	if err == nil || !errors.Is(err, ErrFilesystemUnsupported) {
		return watches, err

internal/execute/watchmanager/watchmanager.go:281

  • If WatchDirectories returns partial closers together with an error, this success-only branch now drops those live watches without tracking or closing them. The previous implementation explicitly handled that case, and WatchBackend does not require failures to be atomic. Please preserve the rollback so a failing reconciliation cannot leak callbacks/resources.
	if err == nil {
		for i, update := range updates {
			entries[i].closer = closers[i]
			wm.watchedDirs[update.dir] = entries[i]
		}
		return nil

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.

tsc --watch doesn't work on NTFS partitions on Linux tsc --watch does not work in docker

3 participants