microsoft/typescript-go · error
statfs: %w
Error message
statfs: %w
What it means
The final setup step of arming a fanotify watch — statfs(2) to obtain the filesystem id used in the subscription key — failed. The mark and handle already succeeded, so this is almost always a race (directory unmounted or removed between syscalls) or a permission problem; the mark is rolled back before returning.
Source
Thrown at internal/fswatch/fanotify_linux.go:364
return err
}
return nil
}
func (b *fanotifyBackend) markDir(w *dirWatch, path string, markPath string) error {
if err := unix.FanotifyMark(b.fanotifyFD, fanotifyMarkAddFlags, b.markMask, unix.AT_FDCWD, markPath); err != nil {
return maybeWrapUnsupportedFilesystem(err)
}
handle, _, err := unix.NameToHandleAt(unix.AT_FDCWD, markPath, 0)
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.watchersTouchedView on GitHub (pinned to 1bcfa18d79)
Solutions
- Verify the directory still exists (os.Stat) and re-check errno: ENOENT means race — recreate/re-subscribe when the directory reappears
- Subscribe to the parent directory first (to learn about recreation), then watch the target once it exists
- For EACCES on hardened systems, adjust LSM policy for the watching process
Example fix
// before w, err := fswatch.Default().WatchDirectory(tmpBuildDir, cb) // statfs ENOENT: dir removed mid-setup // after: ensure the dir exists, watch parent for recreation os.MkdirAll(tmpBuildDir, 0o755) w, err := fswatch.Default().WatchDirectory(tmpBuildDir, cb)
Defensive patterns
Strategy: retry
Validate before calling
// Avoid the TOCTOU window: verify the directory right before subscribing.
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
os.MkdirAll(dir, 0o755) // recreate, or wait for the creator to finish
}
w, err := fswatch.Default().WatchDirectory(dir, cb) Type guard
func isStatfsFailure(err error) bool {
return err != nil && strings.Contains(err.Error(), "statfs:")
} Try / catch
w, err := fswatch.Default().WatchDirectory(dir, cb)
if err != nil && isStatfsFailure(err) {
if _, serr := os.Stat(dir); serr == nil {
// Directory exists: transient mount race — retry once.
time.Sleep(50 * time.Millisecond)
w, err = fswatch.Default().WatchDirectory(dir, cb)
} else {
// Directory is gone: watch its parent to catch recreation.
w, err = fswatch.Default().WatchDirectory(filepath.Dir(dir), func(ev fswatch.Event) {
if ev.HasCreate() && ev.Path == dir { resubscribe(dir, cb) }
})
}
}
if err != nil { return err } Prevention
- Create target directories before subscribing; never race `rm -rf` of the watch root
- For ephemeral build dirs, subscribe to the stable parent and (re)watch children on create events
- One retry with a short delay resolves most statfs TOCTOU failures; more indicates an unmount
When it happens
Trigger: Watched directory deleted or unmounted between FanotifyMark/name_to_handle_at and statfs (TOCTOU); automount expiry; LSM (selinux) denying statfs on the target; NFS server dropping the export mid-setup.
Common situations: Temporary/build directories being cleaned while a watcher subscribes; tests creating and deleting fixtures rapidly; network mounts with flaky connectivity.
Related errors
- unable to poll: %w
- Error reading from fanotify: %w
- unable to open pipe: %w
- unable to initialize fanotify: %w
- fanotify_mark on '%s' failed: %w
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/afac0b6b60e3c25c.
Report an issue: GitHub.