microsoft/typescript-go · error
%w: %w
Error message
%w: %w
What it means
maybeWrapUnsupportedFilesystem upgrades fanotify syscall errors whose errno is EOPNOTSUPP, ENOTSUP, or ENODEV into a dual-wrapped error that also carries the sentinel fswatch.ErrFilesystemUnsupported ('fswatch: watcher backend unsupported on this filesystem'). This is the library's contract for 'this backend cannot watch THIS filesystem', letting callers branch on errors.Is and pick a different backend while the original errno stays inspectable.
Source
Thrown at internal/fswatch/fanotify_linux.go:374
if err != nil {
// Unmark since we can't track this directory without a handle.
_ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, markPath)
return maybeWrapUnsupportedFilesystem(fmt.Errorf("name_to_handle_at: %w", err))
}
var st unix.Statfs_t
if err := unix.Statfs(markPath, &st); err != nil {
_ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, markPath)
return fmt.Errorf("statfs: %w", err)
}
key := makeFanotifyHandleKey(st.Fsid.Val, handle.Type(), handle.Bytes())
sub := &fanotifySubscription{path: path, watchPath: markPath, dirWatch: w, key: key}
b.subscriptions[key] = append(b.subscriptions[key], sub)
return nil
}
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 {View on GitHub (pinned to 1bcfa18d79)
Solutions
- Branch on errors.Is(err, fswatch.ErrFilesystemUnsupported) and retry the subscription with fswatch.Inotify() or a polling watcher
- Move the working tree to a native Linux filesystem when inotify fanout cost matters
- Keep the raw errno (errors.Is/errno extraction) for diagnostics when reporting upstream
Example fix
// before: treat any error as fatal
w, err := fswatch.Default().WatchDirectory(dir, cb)
if err != nil { log.Fatal(err) }
// after
w, err := fswatch.Default().WatchDirectory(dir, cb)
if err != nil {
if errors.Is(err, fswatch.ErrFilesystemUnsupported) {
w, err = fswatch.Inotify().WatchDirectory(dir, cb)
}
if err != nil { log.Fatal(err) }
} Defensive patterns
Strategy: fallback
Type guard
func isUnsupportedFS(err error) bool {
return err != nil && errors.Is(err, fswatch.ErrFilesystemUnsupported)
} Try / catch
w, err := fswatch.Default().WatchDirectory(dir, cb, opts...)
if err != nil {
if errors.Is(err, fswatch.ErrFilesystemUnsupported) {
// Errno stays wrapped for diagnostics; switch backend for this path.
log.Printf("fanotify unsupported on %s (%v); using inotify", dir, err)
w, err = fswatch.Inotify().WatchDirectory(dir, cb, opts...)
}
if err != nil { return err }
} Prevention
- Make ErrFilesystemUnsupported part of your startup matrix: decide backends per mount, not per process
- Keep the inner errno when logging (errors.Is works through both wraps) so reports stay actionable
- Integration-test your fallback path on CI runners that actually use FUSE mounts
When it happens
Trigger: fanotify_mark or name_to_handle_at returning EOPNOTSUPP/ENOTSUP (FUSE/virtiofs/gRPC-FUSE mounts without file-handle support) or ENODEV (e.g. NTFS via fuseblk); any WatchDirectory on the fanotify backend against such a mount surfaces this wrapper.
Common situations: macOS Docker Desktop / Podman sharing code into Linux VMs; WSL2 /drv mounts; CI on FUSE-backed checkouts.
Related errors
- unable to open pipe: %w
- unable to initialize fanotify: %w
- unable to poll: %w
- fanotify_mark on '%s' failed: %w
- name_to_handle_at: %w
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/8a03a205c25aa5b3.
Report an issue: GitHub.