microsoft/typescript-go · error

Error reading from fanotify: %w

Error message

Error reading from fanotify: %w

What it means

The fanotify event-dispatch loop read(2) from the fanotify fd failed with an errno other than EAGAIN/EWOULDBLOCK (both mean 'queue drained, stop'). Because the fd was opened O_NONBLOCK, a genuine read error means the fd is bad or arguments invalid — the backend terminates and the error propagates.

Source

Thrown at internal/fswatch/fanotify_linux.go:390

func maybeWrapUnsupportedFilesystem(err error) error {
	if errors.Is(err, unix.EOPNOTSUPP) || errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.ENODEV) {
		return fmt.Errorf("%w: %w", err, ErrFilesystemUnsupported)
	}
	return err
}

// handleEvents reads and dispatches fanotify events from the fd.
func (b *fanotifyBackend) handleEvents() error {
	buf := b.readBuf
	watchersTouched := b.watchersTouched

	for {
		n, err := unix.Read(b.fanotifyFD, buf)
		if err != nil {
			if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EWOULDBLOCK) {
				break
			}
			return fmt.Errorf("Error reading from fanotify: %w", err)
		}
		if n == 0 {
			break
		}

		metaSize := int(unsafe.Sizeof(unix.FanotifyEventMetadata{}))
		data := buf[:n]
		for len(data) >= metaSize {
			meta := (*unix.FanotifyEventMetadata)(unsafe.Pointer(&data[0]))
			if meta.Vers != unix.FANOTIFY_METADATA_VERSION {
				return fmt.Errorf("unsupported fanotify metadata version: %d", meta.Vers)
			}
			eventLen := int(meta.Event_len)
			if eventLen < int(meta.Metadata_len) || eventLen > len(data) {
				break
			}

			// FID mode: fd should be FAN_NOFD, but close if somehow set.

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Ensure exactly one owner closes each watcher; never share Close across goroutines
  2. Treat the watcher as dead on this error: drop it and create a fresh subscription if watching must continue
  3. If EIO recurs, check dmesg and the health of the underlying disk/mount

Example fix

// before
go func() { w.Close() }()
go func() { w.Close() }() // second close races event loop read -> EBADF

// after: single-owner shutdown via sync.Once
var once sync.Once
shutdown = func() { once.Do(w.Close) }
Defensive patterns

Strategy: retry

Type guard

func isFanotifyReadFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "Error reading from fanotify")
}

Try / catch

err := watchUntilStopped(ctx, dir, cb)
if isFanotifyReadFailure(err) {
    // Backend fd went bad; rebuild the watcher from scratch (fresh fds).
    err = watchUntilStopped(ctx, dir, cb)
}
return err

Prevention

When it happens

Trigger: EBADF from the fanotify fd being closed by concurrent teardown (double Close, racing goroutines); EINVAL from a kernel/driver mismatch on buffer or flags; EIO from a failing filesystem underneath the watch.

Common situations: Multiple goroutines closing the same watcher; watchers stored in maps cleared during shutdown while events still flow; hardware/filesystem errors on the watched volume.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/ce78f0463c7b5896. Report an issue: GitHub.