microsoft/typescript-go · error
unable to initialize fanotify: %w
Error message
unable to initialize fanotify: %w
What it means
unix.FanotifyInit failed with FAN_CLASS_NOTIF|FAN_CLOEXEC|FAN_NONBLOCK|FAN_REPORT_FID|FAN_REPORT_DFID_NAME. The wrapped errno identifies why: ENOSYS/EINVAL on kernels without fanotify or without FID reporting (pre-5.1), EINVAL where the flag combination is unsupported, EPERM on kernels requiring CAP_SYS_ADMIN, EMFILE on fd exhaustion.
Source
Thrown at internal/fswatch/fanotify_linux.go:233
}
b.pipeWriteFD.Store(-1)
b.watcherBase.init(b)
return b
}
func (b *fanotifyBackend) start() error {
if err := unix.Pipe2(b.pipeFDs[:], unix.O_CLOEXEC|unix.O_NONBLOCK); err != nil {
return fmt.Errorf("unable to open pipe: %w", err)
}
b.pipeWriteFD.Store(int32(b.pipeFDs[1]))
defer func() {
b.closeFDs()
close(b.endedSignal)
}()
fd, err := unix.FanotifyInit(fanotifyInitFlags, unix.O_RDONLY|unix.O_CLOEXEC)
if err != nil {
return fmt.Errorf("unable to initialize fanotify: %w", err)
}
b.fanotifyFD = fd
pollfds := []unix.PollFd{
{Fd: int32(b.pipeFDs[0]), Events: unix.POLLIN},
{Fd: int32(b.fanotifyFD), Events: unix.POLLIN},
}
b.notifyStarted()
for {
_, err := unix.Poll(pollfds, 500)
if err != nil {
if errors.Is(err, unix.EINTR) {
continue
}
return fmt.Errorf("unable to poll: %w", err)
}View on GitHub (pinned to 1bcfa18d79)
Solutions
- Move to a kernel >= 5.13 (ideally current LTS) so unprivileged FID-based fanotify works
- If stuck below 5.13, grant CAP_SYS_ADMIN to the process or run privileged
- Fall back to another backend: fswatch.Inotify() (pure inotify) or a polling watcher
- Check /proc/config.gz or `grep fanotify /boot/config-$(uname -r)` to confirm CONFIG_FANOTIFY=y
Example fix
// before
w, err := fswatch.Default().WatchDirectory(dir, cb, fswatch.WithRecursive()) // fanotify init EINVAL on old kernel
// after: pick a backend that the kernel supports
watcher := fswatch.Default()
if watcher.Name() == "fanotify" && !kernelSupportsFanotify() {
watcher = fswatch.Inotify()
}
w, err := watcher.WatchDirectory(dir, cb, fswatch.WithRecursive()) Defensive patterns
Strategy: fallback
Validate before calling
// Probe fanotify support before choosing the default backend on Linux.
func fanotifyUsable() bool {
fd, err := unix.FanotifyInit(unix.FAN_CLASS_NOTIF|unix.FAN_CLOEXEC|unix.FAN_NONBLOCK|
unix.FAN_REPORT_FID|unix.FAN_REPORT_DFID_NAME, unix.O_RDONLY|unix.O_CLOEXEC)
if err != nil {
return false
}
unix.Close(fd)
return true
} Type guard
func isFanotifyInitFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "unable to initialize fanotify")
} Try / catch
w, err := fswatch.Default().WatchDirectory(dir, cb, opts...)
if err != nil && isFanotifyInitFailure(err) {
// Kernel lacks fanotify FID support or privileges: switch backend.
w, err = fswatch.Inotify().WatchDirectory(dir, cb, opts...)
}
if err != nil { return err } Prevention
- Document a minimum kernel (>= 5.13 for unprivileged fanotify FID) and enforce it in deployment checks
- In containers on hosts 5.1–5.13, add CAP_SYS_ADMIN or pre-select the inotify backend
- Probe once at startup and cache the backend choice instead of failing per-watch
When it happens
Trigger: Running on Linux < 5.1 (FAN_REPORT_FID requires 5.1; unprivileged FAN_CLASS_NOTIF notification groups require 5.13) — EINVAL/EPERM; kernel built without CONFIG_FANOTIFY — ENOSYS; old container runtimes/syscall filters; fd limit reached — EMFILE.
Common situations: Deploying to older LTS distros (Debian 10, CentOS 7, Ubuntu 18.04 kernels); WSL2 with outdated kernel; minimal VMs; CI images pinned to ancient kernels; unprivileged containers on host kernels between 5.1 and 5.13.
Related errors
- unable to open pipe: %w
- unable to poll: %w
- fanotify_mark on '%s' failed: %w
- name_to_handle_at: %w
- statfs: %w
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/59656d0f555628a3.
Report an issue: GitHub.